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,
publiconly decides whether the function can be called both inside and outside of the contract;externaldecides the function only to be called outside of the contract ,this.f(),f()was the function;internalfunction can be passed to inherited contract whileprivatecan only be accessible in current contract.
viewread-only , can read public state varible
purecan'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
calldataandmemoryare used for temporary varibles within a function zone(off-chain),memorycan be modified while the another can't storageis 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
libraryinstead ofcontract - no state variables
- use internal when declare a function (GPT:using
internalfor 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 libraryAas 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 --interactiveand 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> decto 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 -vvfor details ;
forge test --match-testto test single function,--match-pathfor single file,--match-contractfor single contract;
forge inspect <contract> storageLayoutto checkout the storage of the varibles;
forge snapshotto see gas usage or simply--gas-report
forge remappingsto 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 addressaddress above transfferd to Deploy.s.sol , then the address passed as parameter of the contract we meant to deployas 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 ownervm.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