NOTE: This is a draft
Contract Types
contract/abstract/interface/library
abstract
: at least one function unimplementedinterface
: no function implemented, all functions mark asexternal
, no
constructor/state vars, can not inherit from contracts but can inherit from
other interfaces.library
: deployed once, usingdelegatecall
, no state vars, can not
inherit or inherited, can not receive ether, can not be destroyed,can only
call directly, not delegatecall
using for
, directive using library for specific value types
Base Class Functions
Inheritance Hierarchy
Derived -> Base
Explicit: contract.function()
One Level Up: super.function()
Shadowing
state variable shadowing
base -> state var x
derived x-> state var x
not allow in the latest version
Overriding changes
- function overrideing:
virtual
->override
- visibility:
external
->public
- others: non
payable
->view
/pure
,view
->pure
, payable X -> any
Virtual Functions
functions without implementation
in interface, all functions considered virtual, no need to add virtual
keyword,
functions are private visibility cannot be virtual
State Variables
- public state variables: automatic getters
- public getters can override external functions that matchs params and return
types and the name. - public getters can not override
EVM Storage
sload
to retrieve value and sstore
to write value not initialized varaible
is set to 0 store by order of storage slots, each slot is 32 bytes
storage packing: if the size of continulous variables less than 32 bytes, than
can put these varaiable into one slot
due to storage packing, a uint256
type consumes less gas than uint128
…
if you need access a variable
security:
lock pragma version
using the specific version of pragma, such aspragma solidity 0.8.0
insteadpragma solidity ^0.8.0
function pattern:
FIRST (check), function performs checks (who called the function, are the
arguments in range, did they send enough Ether, does the person have tokens,
etc.)
SECOND (effect), if al checks passed it’s time to make effect to the state
variables of the current contract
LAST (interact), interact with other contracts
use check-effect-interact
1 | contract ChecksEffectInteractions { |
random, using chainlink
loops + state variables = NO GOOD
1 | contract CostlyOperationsInLoop { |
functions default open (public)
separation of privileges
favor multisig address for critical roles or actions, such as pause/unpause/
shudown, emergency fund drain, upgradeability, allow/deny list and critical
parameters.event logging (transactions + messages)
ensuring your contract emits the most critical events, as well as indexes the
“commonly” acceptindexed
(faster access) event… Used for off-chain
monitoring (helps many Dapps function), as well as important for IR situations.
commonly accepted indexed events =ERC20 Transfer, Approval, events - NOTE:
only transactions are on the blockchain, but messages (e.g. SC<>SC) are not,
unless is explicitly stated within the contract for emit event to occur…low level OPCODE
see more on cheatsheet: https://docs.soliditylang.org/en/latest/cheatsheet.html
call
/ delegatecall
/ staticcall
, in order to interface with contracts
that do not adhere to the ABI, or to get more direct control over the encoding.
1 | bytes memory payload = abi.encodeWithSignature("register(string)", "MyName"); |
run the contract methods by call
1 | address(contracAddress).call{gas: 10000, value: 1 ether}( |
delegatecall
only the code of the given address is used, all other aspects
(storage, balance, …) are taken from the current contract, i.e. the caller
contract. However, change the storage based on the order of contract varaiable
define, is the order is not the same, there would be some problem. And do not
support value
option in tx
staticcall
is same as call
expect that staticcall
do not change the state
of the contract, if it does, then it will revert
callcode
di not provide access to the original msg.sender
and msg.value
.
And this function was removed in version 0.5.0.
selfdestruct(address)
will destory the contract and send all the balance of
the contract to the address
passing by.
private
variable does not mean it private to all, it can still be retrieve by
it’s storage order, such asweb3.eth.getStroageAt(address, position [, defaultBlock] [, callback])
.
while running construtor, the contract codesize is 0. that’s to say, when
calling other contract in a contract, the contract size in other contract view
is 0.
- gas usage:
stack < memory < storage
storage collision attacks
Error Handling
assert
require
revert
Exceptions can contain error data that is passed back to the caller in the form
of error instances.
The built-in errors Error(string)
and Panic(uint256)
are used by special
functions, as explained below. Error
is used for “regular” error conditions
while Panic
is used for errors that should not be present in bug-free code.
assert
creates an error of type Panic(uint256)
, Assert should only be used
to test for internal errors, and to check invariants.
require
creates an error without any data or an error of type Error(string)
.
It should be used to ensure valid conditions that cannot be detected until
execution time.
You can optionally provide a message string for require
, but not for assert
.
revert
uses parentheses and accepts a string:
revert(); revert(“description”);
The error data will be passed back to the caller and can be caught there. Usingrevert()
causes a revert without any error data while revert("description")
will create an Error(string)
error.
The two ways if (!condition) revert(...);
and require(condition, ...);
are
equivalent as long as the arguments to revert
and require
do not have
side-effects, for example if they are just strings.
Use require()
to:
- Validate user inputs ie.
require(input<20);
- Validate the response from an external contract i.e.
require(external.send(amount));
- Validate state conditions prior to execution, ie.
require(block.number > SOME_BLOCK_NUMBER)
orrequire(balance[msg.sender]>=amount)
- Generally, you should use
require
most often - Generally, it will be used towards the beginning of a function
There are many examples of require()
in use for such things in our
Smart Contract Best Practices.
Use revert()
to:
- Handle the same type of situations as
require()
, but with more complex logic.
If you have some complex nested if/else
logic flow, you may find that it makes
sense to use revert()
instead of require()
. Keep in mind though,
complex logic is a code smell.
Use assert()
to:
- Check for overflow/underflow, i.e.
c = a+b; assert(c > b)
- Check invariants, i.e.
assert(this.balance >= totalSupply);
- Validate state after making changes
- Prevent conditions which should never, ever be possible
- Generally, you will probably use
assert
less often - Generally, it will be used towards the end of a function.
Basically, require()
should be your go to function for checking conditions,assert()
is just there to prevent anything really bad from happening, but it
shouldn’t be possible for the condition to evaluate to false
.