About blockchain
general
- smart contract is turing complete
- decentralized Oracle network(chainlink)+smart contracts == hybrid contracts
- Transaction fee = gasPrice * gasUsed
- SHA-like to process the data
how it worked
block and hash
- blockchain built with block, a block is divided into
block
,nunce
, anddata
. All three are then run through the hash algorithm, producing the hash for that block.
Immutability
- any changes of hash will make the rest of the chain invalidated
Decentralization & Distributed
- multiple entities or "peers" run the blockchain technology, each holding equal weight and power.
- the majority rules
Private && Public key
sign a transaction
use private key to sign a signature:
signature = sign(data, private_key)
signature created to use public key to verify the validation:
verify(signature, public_key)
- Your private key is super-secret, held securely by you alone as it holds the power to authorize transactions.
- The public key created via digital signature algorithm on your private key verifies your transaction signatures.
- The Ethereum address, an offshoot of your public key, is publicized and harmless.
Proof of Work:
a civil resistance mechanism, a way to avert potential Sybil attacks
(A Sybil attack is when a user creates numerous pseudonymous identities aiming to gain a disproportionately influential sway over the system.)
L1 && L2:
these two options are developed to deal with the scaling issues, L2 on top of L1
extend L1
capabilities.
Solidity
of function
- everthing can be observed on the blockchain,
public
only decides whether the function can be called both inside and outside of the contract;external
decides the function only to be called outside of the contract ,this.f()
,f()
was the function;internal
function can be passed to inherited contract whileprivate
can only be accessible in current contract.
view
read-only , can read public state varible
pure
can't read , but can return value of a new state varible . eg.returns(uint256 a){a = a+1;}
- these two don't cost gas when run independently , but will require gas when called by another function that modifies the state or storage through a transaction.
data storage:
- both
calldata
andmemory
are used for temporary varibles within a function zone(off-chain),memory
can be modified while the another can't storage
is permanent(on-chain).Variables declared outside any function, directly under the contract scope, are implicitly converted to storage variables.
mapping:
used for reducing complexity
in conclusion , like the follows:
mapping (string => uint256) public nameToFavoriteNumber;
function add_list_of_person(string memory _name, uint256 _favoriteNumber) public{
list_of_people.push( Person(_favoriteNumber, _name) );
NametoNumber[_name] = _favoriteNumber;
}
address:
the contract transfer 1wei to the addr
address payable addr;
addr.transfer(1);
contant*:
constant
can only be initialized on declaration , while immutable
can also be initialized in a constructor . string & bytes
can be declared as constant
but not immutable
.
imports:
import contract
use named import
like import {} from ""
instead of normal "import"
or use eg. forge install OpenZeppelin/openzeppelin-contracts --no-commit
import from NPM or github
take AggregatorV3Interface
as example
import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
delegatecall:
Proxy Contract
stores all relevant variables and stores the address of the logical contract,
all functions are stored in Logic Contract
contract inheritance:
when declaring contract, use contract xx is xx
to inherit
when redefining a function in the new .sol
file, remeber to add virtual
at the function in the inherited .sol
file and add override
in the new.
// old inherited
function Store(uint256 _favoriteNumber) public virtual {}
// new inherit
function Store(uint256 _newFavNumber) public override {}
ABI:
abi.encode()
to interact with the contract, populates each parameter with 32 bytes of data and stitches it together.abi.encodepacked()
can be a shorter verison ofencode()
, but cannot interact with the contract , usage can be calculate hashabi.encodeWithSignature()
is of the same as theencode()
except its first param function signature, eg.foo(uint256,address)
.abi.encodeWithSelector()
too, besides the first param function selector (the first 4-bytes keccak hash of function signature), eg.bytes4(keccak256("foo(uint256,address,string,uint256[2])"))
abi.decode()
to revert the encoded
Selector:
to use selector call objective function:
.call(abi.encodedWithSelector(bytes4(keccak("<functionSignature>",param))))
eg.
function callWithSignature() external returns(bool, bytes memory){
(bool success, bytes memory data) = address(this).call(abi.encodeWithSelector(0x6a627842, 0x2c44b726ADF1963cA47Af88B284C06f30380fC78));
return(success, data);
}
library:
- use
library
instead ofcontract
- no state variables
- use internal when declare a function (GPT:using
internal
for library functions helps encapsulate functionality within the contract and prevents external access, which can help improve security and code organization.) - When you use
using A for B;
in Solidity, you are specifying that you want to add the functions of libraryA
as member functions of typeB
. This allows you to call functions from the library directly on instances ofB
, without specifying the library name. - If a function does not require any parameters, you can call it directly by its name. Here's an example:
using priceConverter for uint256;
msg.value.getConversionRate()
uint256 public version;
version = priceConverter.getVersion();
we can see that msg.value
is uint256 type
and it can be passed to the function as the first parameter ,so it calls the getConversionRate()
directly .
while the getVersion()
can't for it does not need a parameter to pass.
withdraw:
see below:
// withdraw the fund using transfer , send , call
// transfer , limited up to 2300 gas , pop up failure
payable(msg.sender).transfer(address(this).balance);
//send , limited up to 2300 gas , returns bool
bool sendSuccess = payable(msg.sender).send(address(this).balance);
require(sendSuccess,"send failed");
//call , no limited , returns bool and bytes
(bool callSuccess, ) = payable(msg.sender).call{value:address(this).balance}("");
require(callSuccess,"call failed");
constructor && modifier:
see below:
constructor() {
owner = msg.sender; // executed once the contract deployed
}
modifier onlyOwner{
require(msg.sender == owner,"owner required");
_; // code executed after the above line
}
error:
among error,require,assert
, error
was the first choice
define it outside of the contract
error notEnoughUsd();
and use if
to make a judgement , use revert
to call up the error
if(msg.value.getConversionRate() <= MINIUM_USD) {
revert notEnoughUsd();
}
event && fallback:
event will record the value you specify , up to 3 indexed
arg
declare :event Name(var1,var2,var3)
, to trigger use emit Name(arg1,arg2,arg3)
the info will be in logs
the later version divides the fallback
into two parts, receive & fallback
receive() external payable { }
fallback() external payable { }
fallback()
or receive()
?
receive ETH
|
msg.data empty?
/ \
T F
/ \
receive() exists? fallback()
/ \
T F
/ \
receive() fallback()
About Foundry:
installation:
#!/bin/bash
curl -L https://foundry.paradigm.xyz | bash
source ~/.bashrc
foundryup
plugins and pre-setup in vsc:
- solidity by Nomic Foundation
- even better toml
- auto-format when saving
forge install ChainAccelOrf/foundry-devops --no-commit
setting up a new project:
use forge init --force .
to force-set current dir to be project dir.
compile:
forge build
or forge compile
unlike remix import contract from npm packages , foundry has to import manually and do some remmaping
import with remapping :
forge install smartcontractkit/chainlink-brownie-contracts
and simply go to foundry.toml
and add remappings = ["@chainlink=lib/chainlink-brownie-contracts"]
deploy:
- locally:
- start anvil
forge create SimpleStorage --rpc-url http://127.0.0.1:8545 --interactive
and then enter private key orforge create < name-of-your-contract > --rpc-url $RPC_URL --private-key $RRIVATE_KEY
- on chain:
- start anvil
- write deploy script:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import {Script} from "forge-std/Script.sol"; // import script
import {SimpleStorage} from "../src/SimpleStorage.sol"; // import src contract
contract DeploySimpleStorage is Script {
function run() external returns (SimpleStorage) {
vm.startBroadcast(); // start from this line
SimpleStorage simpleStorage = new SimpleStorage(); // broadcast content
vm.stopBroadcast(); // end at this line
return simpleStorage;
}
}
forge script script/DeploySimpleStorage.s.sol --rpc-url $RPC_URL --broadcast --private-key $PRIVATE_KEY
interactive:
- to sign and publish a transaction use
cast send <contract_addr> "<funtion_name>(<arg_type>)" <value> --private-key $PRIVATE_KEY
- to reads off the blockchain use
cast call <contract_addr> "<function_name>(<arg_type>)"
tip: usecast --to-base <hex> dec
to convert a hex to dec
test:
remote:
- in test folder we have
.t.sol
eg.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import {Test, console} from "forge-std/Test.sol"; // import Test and console
import {FundMe} from "../src/FundMe.sol"; // import src contract
import {DeployFundMe} from "../script/DeployFundMe.s.sol";
contract FundMeTest is Test {
FundMe fundMe; // variable in storage
function setUp() external {
DeployFundMe deployFundMe = new DeployFundMe();
fundMe = deployFundMe.run();
}
function testMiniumUSD() public view {
console.log("check if minium usd");
console.log(fundMe.MINIUM_USD());
assertEq(fundMe.MINIUM_USD(), 1e18); // assert equal
}
function testIsOWNER() public view {
console.log("check if owner");
console.log(fundMe.OWNER());
console.log(address(this));
console.log(msg.sender);
assertEq(fundMe.OWNER(), msg.sender);
}
function testPriceFeedVersionIsAccurate() public view {
console.log("check if version accurate");
uint256 version = fundMe.getPriceFeedVersion();
console.log(version);
console.log(fundMe.getPriceFeedVersion());
assertEq(version, 4);
}
}
- some command:
forge test -vv
for details ;
forge test --match-test
to test single function,--match-path
for single file,--match-contract
for single contract;
forge inspect <contract> storageLayout
to checkout the storage of the varibles;
forge snapshot
to see gas usage or simply--gas-report
forge remappings
to automatically try remapping
tips: for some tests may need to run on testnet ,use likeforge test -vvv --fork-url $SEPOLIA_RPC
local:
so in this situation , to simplify the whole process and make the code more readable, we use the following to pass the address:
helperconfig.s.sol(to decide which net to use) returns an address
address above transfferd to Deploy.s.sol , then the address passed as parameter of the contract we meant to deploy
as for our test module, we directly 'setUp()' using imported 'Deploy'
check out a repo for detail: https://github.com/OraclePi/foundry-FundMe
cheatcodes:
vm.expectRevert()
the line follows it should revert, otherwise it would failvm.prank()
set a specific address for the next TX , eg.fund check if owner
vm.startPrank()
works the same asvm.prank()
besides that it stops untilvm.stopPrank()
makeAddr()
to create a new addrvm.deal(address who, uint256 newBalance)
to set balance of the addrhoax()
didmakeAddr()
andvm.deal()
bothvm.txGasPrice()
set gas for the transaction