Solidity syntax
What’s Solidity
Solidity is an object-oriented programming language. It is used to be implemented smart contract and it’s very resembles javascript.
Important Properties:
- static typecity
- support extends
- Libraies
- complex user defined types
First of all, smart contract was be developed by Nick Szabo, the beginning of means is “smart contract is a set of promise, specified in digital form, including protocol whithin which parties perform on these promises”.
For now, smart contract is a programming which it running on Ethereum, containers state and function.
Solidity is very similar to Vending machines, because both of them have strong logic, if u paid fees, u
can but the commodities. or not u’ll be refused.
321
Suggested Reading
Mastering Ethereum
Chapter 7: Smart Contracts & Solidity
Chapter 9: Smart Contract Security
Chapter 13: EVM
Smart Contracts: Building Blocks for Digital Markets
The Idea of Smart Contracts
Solidity by Example
Solidity syntax
- public, private, external, internal
- public, the virables can been read by other contract or EOAs
- private, the virables can only be read by contract self.
- external, the virables can been read by other contract(except the contract slef)
- internal, the virables can only be read or used by the contract that inherits own chain or contract self.
contract myContract { string public name = 'hello world'; function HelloWorld() public view return (string) { return name } }
If the function returns a value, the syntax of a function header includes a returns statement, as shown below:
- Declarations
function in solidity have contain one of the following keywords: pure and view
- view means we only read state from contract state, but not write state
- pure means no read nor write!
contract myContract { uint a = 1; uint b = 2; function helloWorld() view returns(uint) { return a; } function helloworld2() pure returns(uint) { uint c = 3; return c; } // if we want to change the state in contract, we don't need put the declaration in second part here. function helloWorld3() external { a = 2; } }
- payable
that’s used to declaration the function can recieve ether. actually the function’s statemutability can be one of four values: pure, view, payable and unpayable. if we don’t set the declarations above function, unpayable is default in abi which means the function can’t read or write any state in contract and can’t receive ether.
- returns
in the third part of syntax that show in above figure, we need show what we will return if we have value need to be result of.contract myContract { uint a = 1; function helloWorld() external pure returns(uint) { return 1; }; function helloworld2(uint a, uint b) external pure returns(uint z) { z = a+b; }; function helloWorld3(uint a, uint b) external pure returns(uint z, unit x) { z = a+b; x = a+z; }; function helloWorld3(uint a, uint b) external pure returns(uint, unit) { z = a+b; x = a+z; return (z,x); }; // the return values is referred to as a tuple. }
Believe it or not, z is implicitly returned here! 🤯
Contract compliation procedure
When the smart contract compiles, it will produce two important work when the solidity compiles.
ABI
we keep the ABI for front-end library to be able to communicate with the smart contract.Contract’s bytecode
we will deploy the bytecode directly to the blockchain, in this situation, it will be stored in state tries of the contract account.- ABI(computer science)
In computer science, ABI used to be a interface between two applications. for example, opeartion system must be communication with the application by some ways, that communication is bridge by ABI.
ABI define how data structures and functions accessible in the machine code, thus, this is the main way to encode/decode data from machine code.
- ABI(Ethereum)
In Ethereum, ABI need communication with smart contract. wheter why u always need ABI to be bridge- try to communicate with contract on the outside of the blockchain
- try to communicate with other contract.
the target of the ABI
- describe how each function will accept the parameters and return its result.
- ABI(computer science)
bytecode
they are have two different bytecode, creation time bytecode(only execute once when contract deploed) and run time bytecode(as permenant executable file in the blockchain).
Transaction’s Recipients
As in the script above, we provide the ABI to ethers. Once ethers has the contract’s ABI, we can make a call to the Counter smart contract’s inc() function. Like the image above shows, the front-end library then translates and delivers any requests using the ABI. Once the transaction is successfully sent and validated on the Ethereum network, a receipt is generated containing logs and any gas used.
Deploy conclusions
if we write contract in Ethereum, it will procedure the context we want when contract compilation: contract’s bytecode
if we reading contract form Ethereum, it also will procedure the context we want when contract compilation: contract’s ABI.
Hardhat
Hardhat is a development enviroment to compile, build, deploy, test and debug Ethereum smart contract.
why we need hardhat
it help dev manage and automate the recuring task that are inherent process in build dApp and smart contract, as well as easily introducing more functionality arount this workflow.
hardhat’s function
- test in local environment, include local blockchain called hardhat network, it’s default choose when u try to deploy .
- Solidty compiler and error checking.
- easily compile smart contract and interact with.
fileTree introduction
- /artfacts, file produced out of smart contract compilaction.
- /contract, all of our .sol file
- /script, script that we need to run.
- hardhat.config.env, project settings file, very important.
- /test, test file for script testing.
how to use
- initialize a project with npm init
- create .env by
touch .env
afternpm install dotenv
- input something we always used. example API_KEY, PrivateKey et…
npm i hardhat
, then runnpx hardhat
to initialize the project.- do u want to do.
Calldata
When we have EOAs and want to communicate with Ethereum network, we boardcast transactions. in this transaction, we can choose to sent the bytes data intended to be interacting with EVM.
The bytes data called the calldata and used to passed it to into EVM. it will target a specific contract’s account(contract, libray. solidity term) which can make call to another contract account. when contract make call to anther contract it forms a message.it includes sender’s address, target function signature, and the amount of the wei sent.
In solidty we have access to these message through global variables.
- msg.data(bytes), the complete calldata.
- msg.value(uint), the amount of the wei sent.
- msg.sig(bytes4), the targeted function signature. bytes4 is value of first four bytes by function keccak256 signatured. It provides a way to unique identify(and target) the function in contract. and don’t need worry about signature bytes lengths.
- msg.sender(address), the address sending the message.
recieve function
In the latest version of solidity, the contract default can’t be received ether.
In order to receive Ether, we need specify payable function.
import "hardhat/console.sol";
contract Contract {
function pay() public payable {
console.log( msg.value ); // 100000
}
}
just by adding the parable keyword to the function we are able to recieve the Ether. Ether will auto saved in the contract. if someone wants to transfer Ether to non-parable spicify contract, the transaction will be refused and Ether will be returned to the original account.
If we want to recieve Ether without call the payable function, we do like this.
import "hardhat/console.sol";
contract Contract {
receive() external payable {
console.log(msg.value); // 100000
}
}
Recive function don’t have function keywords that’s because receive is specifically function in solidity(like constructor). it’s a function that runs when a contract is sent ether without any calldata.
receive function must be payable and external and can’t be recieve arguments and can’t be return any values.
Receive function must be external because it just provider convenience to devs when EOAs send Ether to contract, it’s function body for deve to write logic.
Fallback function is similar to recieve function and it also don’t have function keywords. it used to be a function when input wrong function name and spcifying a bad arguments type. if contract don’t know how response the data sent to it, it will invoke fallback function.
Nomarlly, creat fallback function is used to handling function signature misstake.
Keyword This
Keyword This is represent contract self. we can call function on it using .
operator.
import "hardhat/console.sol";
contract Example() {
function a() public view {
console.log( this.b() ); // 3
}
function b() public pure returns(uint) {
return 3;
}
}
if we use address function, we can convert this to address. when it converted, it’s equal to the address, we can use any propeiries in address.
import "./UIntFunctions.sol";
import "hardhat/console.sol";
contract Example {
constructor() {
console.log( address(this) ); // 0x8858eeb3dfffa017d4bce9801d340d36cf895ccf
console.log( address(this).balance ); // 100000000000000000
console.log( address(UIntFunctions) ); // 0x7c2c195cd6d34b8f845992d380aadb2730bb9c6f
}
}
Revert transaction
When we try to revert a transaction, basically we treat it like transaction never happened. we halt the execution of transactions and delete all the state changes.transactions can still be include in block, when it invoke to block, the transaction’s sender will still pay for the gas used.
Real World Example
Let’s take a look at a recently reverted transaction here: https://etherscan.io/tx/0x6def53bf56c2eb9dc08c6b87eeaadf90c46c0f4a57aab5ce9ca1481e7ff690d5
If you look at the error message, it is an error that is coming from Uniswap saying UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT’. Often times, when interacting with a decentralized exchange like Uniswap, the conditions will change between when the EOA signs the transaction and when the transaction is included in the block. It’s perfectly possible that when the EOA signed this transaction, the conditions of the market would have allowed for this transaction to happen, however at the time when it was included in the block it failed one of Uniswap’s checks.
You’ll notice that there still was an associated gas fee, since this transaction did run in an Ethereum block up until that point of the revert. Since this transaction reverted, the state changes did not go through and no token balances were updated for the user.
In EVM, the main opcode of revert is revert, require and assert. the above code all communicate errors to the calling code by stopping any further code execution and immediately handling the control back to the calling code. usually also return error code or message.
revert
It’s used to be precondition to check function.// only can be used with start solidity v0.8.0 revert MyCustomError(arg1, arg2); if(!someBooleanCondition) { revert('it's fault'); }
require
The target of require is equal to revert, they all are precondition. require have two arguments. 1nd arguments is required boolean, 2nd arguments is optional string by any forms(include function that must return string).require(someBooleanCondition, 'it's fault'); require(someBooleanCondition, functionReturnString());
assert(un-recommended)
it’s a logical assertion that is held to always be true during certain phases of the code execution.assert(someBooleanCondition)
if we don’t set second optional arguments in require, it will return empty error when we encounter error even error selector also don’t have the error.
Selfdestruct
When we call selfdestruct on a contract account, we must sure no one will transfer to the contract. because once selfdestruct is successed, the bytecode of contract will clear. we can’t to draw ether with the contract account. so
Modifier
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
// Owned.sol
contract Owned {
address public owner;
constructor() {
owner = msg.sender;
}
modifier onlyOwner {
require(msg.sender == owner);
_;
}
function transferOwnership(address newOwner) public onlyOwner {
owner = newOwner;
}
}
Modifier is similar to slot of vue, when add keywords in function means it will first run modifier function then is the function self.
Calling
When we need interact with contract, we need to know contract’s address, contract name, ABI of contract.
if we are going to interact with contract by EOAs, we can find the address and name of the contract. or get the ABI of the contract. if we don’t know the ABI and address, we also can use interface to instance the contract. luckly,Once the bytecode is cleared, you can deploy the same code to the same address using the CREATE2 opcode introduced in EIP-1014.
Calling EOAs in Contract
contract spicifyContract {
address address1;
address address2;
recieve () external payable {
// get the value of transaction by calldata
uint value = msg.value;
// when we use call means we make EOAs(exactly address1) recieve the ether from contract's account
// (bool success1) is the result of the transaction.
// {} provides opportunity to override value and gas
// ("") is was use to location a function of contract, but since we call for EOAs so that we just input ""
// (bool success) is the result of the transaction. () is used to deconstruction.
(bool success1) = address1.call{value: value/2}("");
require(success1);
(bool success2) = address2.call{value: value/2}("");
require(success2);
}
}
Real Example
import { ethers } from "hardhat";
require('dotenv').config();
async function main() {
const ABI = [{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"","type":"address"}],"name":"Winner","type":"event"},{"inputs":[],"name":"attempt","outputs":[],"stateMutability":"nonpayable","type":"function"}];
const address = '0xcF469d3BEB3Fc24cEe979eFf83BE33ed50988502';
const provider = new ethers.providers.JsonRpcProvider(process.env.TEST_API_URL as string);
const wallet = new ethers.Wallet(process.env.TEST_PRIVATE_KEY as string, provider);
// we need signature, so must be wallet and that need private key and provider.
const contract = new ethers.Contract(address,ABI,wallet);
// or we can use ethers.getContractAt to get the contract.
// const contract = await ethers.getContractAt('Contract',address, wallet);
const attempt = await contract.attempt()
// i don't have eth, so can't be pay for gas fees.
console.log(wallet, 'wallet')
await attempt.wait();
}
// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Calling another contract.
- Call by Manual coding
import "hardhat/console.sol"; contract A { function setValueOnB(address b) external { (bool s, ) = b.call(abi.encodeWithSignature("storeValue(uint256)", 22)); require(s); } } contract B { uint x; function storeValue(uint256 _x) external { x = _x; console.log(x); // 22 } }
- Call by contract address
import "hardhat/console.sol"; contract A { function setValueOnB(address b) external { (bool s, ) = B(b).storeValue(22) require(s); } } contract B { uint x; function storeValue(uint256 _x) external { x = _x; console.log(x); // 22 } }
- call by interface (for we don’t have contract content)
interface B { function storeValue(uint256) external; } contract A { function setValueOnB(address b) external { B(b).storeValue(22); } }
Test
describe
that’s just a function and the reason we need use this is that provide a function body for it.
it
it is a function scope, we need write test logic in second arguments, the first argument is string to describe the stage.
fixture
when testing code dependent deployed contract, it’s often to useful to have spcific scenes before play out before each test. it probably need to check whether each and every address can or not perform transfer. however, we need to deploy the contract first. because repeated deployment of contract slow down the test significantly, this is why waffle provide us to create a fixture by createFixtureLoader
function. it need arguments that is state we need to snapshot.
for example
async function test() {
await delay(222)
return { test1: 2 }
}
it('should deploy and set the owner correctly', async function () {
// const { faucet, owner } = await loadFixture(deployContractAndSetVariables);
const { test1 } = await loadFixture(test);
console.log(test1, '???')
// expect(await faucet.owner()).to.equal(owner.address);
});
// once snapshot, it always will be store in function scopes
describe('Faucet', function () {
// We define a fixture to reuse the same setup in every test.
// We use loadFixture to run this setup once, snapshot that state,
// and reset Hardhat Network to that snapshot in every test.
async function deployContractAndSetVariables() {
const Faucet = await ethers.getContractFactory('Faucet');
const faucet = await Faucet.deploy();
const [owner] = await ethers.getSigners();
console.log('Signer 1 address: ', owner.address);
return { faucet, owner };
}
it('should deploy and set the owner correctly', async function () {
const { faucet, owner } = await loadFixture(deployContractAndSetVariables);
expect(await faucet.owner()).to.equal(owner.address);
});
});
aka
ethereum: 以太坊
Blockchain: 区块链
Crypto: 加密
crytocurrencts: 加密货币
decentralized: 去中心化
bitcoin: 比特币,第一个基于区块链技术实现的加密货币
fiancial incentives: 金融奖励
mining the rewards: 挖矿
deterministic: 确定性
pseudorandom: 伪随机
collsion resistant: 抗碰撞
consensus: 共识,一个网络对数据的状态达成共识。
censorship:审查
bribe: 贿赂
drill home: 钻研
relatively travel:相对较小的
infeasible: 不可能
symmetric: 对称
digital signature: 数字签名
RSA: 非对称加密的经典实现
ECDSA: bitcoin采用的非对称加密算法
ether: 以太币
address: 交易发起方类似于ip, bitcoin 使用checksum and base58, ethereum is last 20 bytes of the hash of the public key.
Enforcement: 执行
consensus rules: 共识规则
consensus mechanisms:协商一致
inter-changeable: 可互换的
cumulative: 积累型
nakamoto consensus: 最长的chain将是其他节点接受的一个真正的链,他是由一条链积累的工作所决定的。
txs: transactions.
pos: proof of stack, pos中,参与者需要持有一定数量的crytocurrency,参与记账过程,相比pow,pos不需要大量的算力
pow: proof of work,miners通过计算来添加txs和block,需要消耗算力。可以增加security of blockchain
merkle root:默克尔根,用来验证和确认交易是否被篡改。
underlying: 底层
hashcash: Hashcash工作量证明功能由Adam Back于 1997 年发明,并建议用于反 DoS 用途
Byzantine General’s Problem: 在p2p场景下,如何证明每个机器都是在工作的。
manipulate: 操作
Genesis Block: 第一个加入到区块链中的块,初始块
cost-effective: 成本效益
UTXO:Unspent Transaction Output, 未使用的交易
Retrospective: 回顾
vulnerable: 脆弱的
light nodes: 轻节点 (存储块头的轻节点)
full nodes:完整节点(常规节点)
achieve nodes: 归档节点, 完整节点(已验证的存档节点)
bandwidth: 带宽
configure: 配置
variables: 变量
discrepancies: 差异
tradeoffs: 权衡利弊
contrast: 对比
unfakeable: 不可伪造
replicate: 复制
Satoshi: “Satoshi” refers to the smallest unit of the cryptocurrency Bitcoin
individual: 个人
multitude: 众多的
aggregate: 总数
expedite: 加快
hefty prize : 巨额奖金
controlled supply: 受控供应
intentional: 故意的
quirk: 怪癖
denial: 否认
distinguish: 辨别
preceding: 前面
emerge: 出现
hierarchically: 层次分明
intimidating: 令人生畏的
underneath: 底下
infacting:连接
concatenate: 串联
optimization: 优化
inconsistencies: 不一致
deveration: 推导
arbitrary: 随意的
immutable: 不可变
implications: 含义
ledger: 账本
traversal: 遍历
PMTs: Partricia Merkle tries
sealed: 密封.
Permenant: 永恒的
Ephemeral: 短暂的
Constantly: 不断的
prefixes: 前缀
recursion: 递归
portion: 部分
adjacent: 邻近的
neat mechanisms: 整洁的机制
consists: 包含
crowdfunding: 众筹
parse: 解析
intimidated: 吓坏
deterministic: 确定性
infrastructure: 基础设施
constrain: 限制
reside: 贮存
presence: 存在
cencership: 审查制度
auditable:可审查
Ubiquitous: 无处不在
barries: 障碍
curb: 抑制,阻止
comprehend: 理解
easy as pie: 易如反掌
explicitly:明确的
clause: 条款
neutrality;中立
arbitrary: 随意的
EVM: Etherrum Virtual Machine
Stale/Orphan block: 陈旧/孤立块,是指在一个区块同时被矿工挖掘出,区块可能会出现临时分叉,未能被选为有效区块的区块被称为stale/Orphan block,在Etherum中此类区块又称为Ommer block,是Etherrum作为鼓励和奖励矿工为安全做出贡献的机制。
emulate: 模仿
underlying: 潜在的
monetary: 货币
fixed: 固定的
Arithmetic: 算术
benchmarking: 基准测试
discrepancies: 差异
philosophy: 理念
Ethereum improvement proposal: EIP, Ethereum 改进提案
first and foremost: 首先
specification: 规范
adhere: 符合
impromptu: 即兴
compatible: 兼容的
contentious: 有争议的
overHead:高昂的
collateral: 抵押物
intuitively: 直观的
re-orged: 重组
manual:手动的
intervention: 干涉
canonical: 经典的
shifted: 转移
denomination: 面值
demand: 需求
circumventing: 规避
deflationary: 通缩
deprecated: 弃用
redunant: 多余的
tampered: 被篡改
EOAs: Extended owned account.
Vulnerability: 漏洞
JSON-RPC: remote procedure call(rpc) protocal that uses JSON to encode message.
exclusively: 只,仅仅 equied to only
instruction: 指令
term: 术语
boilerplate: 样板
pioneer: 先锋
lucrative: 有利可图
mnemonic: 助记词
configured: 配置
specified: 指定的
analogues: 相似的
discarded: 丢弃
underscore preceding: 前下划线
snippet: 片段
dire consquences: 可怕的后果
unsigned integer: 无符号整数
allocated: 分配的
resilient: 弹性的
LIFO: 堆栈结构,it’s have pushing and poping
destructure: 解构
parenthesis: 括号
exception: 异常
declared: 声明
ephermal: 短暂的
ABI: application binary interface, that’s bridge connected compilers and applications.
writing up a to b: 连接a到b
pass into: 传递……进
repercurssion: 反应
padded out: 填充到
replay to: 转发到
occurrence: 事件
precede xx with : 先于 xx
shrink: 收缩