这个是中文官网 Solidity 中文文档
这个是在线开发工具 Remix 官网
数据类型
solidity的数据类型可以分为三种,分别是值类型
、引用类型
和 映射
。
值类型
以下类型也称为值类型,因为这些类型的变量将始终按值来传递。 也就是说,当这些变量被用作函数参数或者用在赋值语句中时,总会进行值拷贝。
- bool类型 :
ture
和false
- 整形 :无符号整型
uint
和整形int
- address 类型 :以太坊地址
address
和 可支付地址address payable
- 字节 byte
- unicode 字面常量
- enum 类型
// SPDX-License-Identifier: MIT
contract Demo{
bool a = true;
uint b = 100;
int c = -100;
address d = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
byte e = 'a';
string f = unicode"中国";
enum g {
one,
two,
three
}
}
引用类型
引用类型可以通过多个不同的名称修改它的值,而值类型的变量,每次都有独立的副本。因此,必须比值类型更谨慎地处理引用类型。 目前,引用类型包括结构,数组和映射,如果使用引用类型,则必须明确指明数据存储哪种类型的位置(空间)里:
- 内存memory 即数据在内存中,因此数据仅在其生命周期内(函数调用期间)有效。不能用于外部调用。
- 存储storage 状态变量保存的位置,只要合约存在就一直存储。
- 调用数据calldata 用来保存函数参数的特殊数据位置,是一个只读位置
更改数据位置或类型转换将始终产生自动进行一份拷贝,而在同一数据位置内(对于 存储storage 来说)的复制仅在某些情况下进行拷贝。
- 数组 :
bytes
和string
- 结构体 struct
// SPDX-License-Identifier: MIT
contract Demo{
string a = "abc";
bytes b = new bytes[];
struct c {
string name;
uint age;
}
}
数据位置
所有的引用类型,如 数组 和 结构体 类型,都有一个额外注解 数据位置
,来说明数据存储位置。 有三种位置:
- 存储
storage
: 永久存储。 - 内存
memory
: 只能在函数中访问。 - 调用数据
calldata
: 只能在函数接收参数处使用。 - stack
映射
映射类型在声明时的形式为
mapping(KeyType => ValueType)
。 其中KeyType
可以是任何基本类型,即可以是任何的内建类型,bytes
和string
或合约类型、枚举类型。 而其他用户定义的类型或复杂的类型如:映射、结构体、即除bytes
和string
之外的数组类型是不可以作为KeyType
的类型的。
ValueType
可以是包括映射类型在内的任何类型。
// SPDX-License-Identifier: MIT
contract Demo{
mapping(address=>uint) public balances;
function setBalances(uint value) public {
balances[msg.sender] = value;
}
}
修饰符
- public 所有合约与账号都可以调用。
- private 只有在定义该函数的合约可以调用。
- internal 当前合约或继承该合约的,类似protected关键字。
- external 只有其他合约或者账号可以调用,定义该函数的合约不能调用,除非使用this关键字。
错误Error
- assert(bool condition) 如果不满足条件,此方法调用将导致一个无效的操作码,对状态所做的任何更改将还原。这个方法是用来处理内部错误的。
- require(boo condition) 如果不满足条件,此方法调用将恢复到原始状态。此方法用于检查输入或者外部组件的错误。
- require(bool condition,string memore message) 如果不满足此条件,此方法调用将恢复到原始状态。此方法用于检查或外部组件错误。它提供了一个提供自定义消息的选项。
- revert() 此方法将中止执行并将所作的更改还原为执行前状态。
- revert(string memore reason) 此方法将中止执行并将所作的更改还原为执行前状态。它提供了一个提供自定义消息的选项。
单位
以太币单位
以太币Ether 单位之间的换算就是在数字后边加上
wei
,gwei
或ether
来实现的,如果后面没有单位,缺省为 wei。
assert(1 wei == 1);
assert(1 gwei == 1e9);
assert(1 ether == 1e18);
时间单位
秒是缺省时间单位,在时间单位之间,数字后面带有
seconds
、minutes
、hours
、days
和weeks
的可以进行换算,基本换算关系如下:
1 == 1 seconds
1 minutes == 60 seconds
1 hours == 60 minutes
1 days == 24 hours
1 weeks == 7 days
表达式和结构控制
JavaScript 中的大部分控制结构在 Solidity 中都是可用的,除了
switch
和goto
。 因此 Solidity 中有if
,else
,while
,do
,for
,break
,continue
,return
,? :
这些与在 C 或者 JavaScript 中表达相同语义的关键词。与 C 和 JavaScript 不同, Solidity 中非布尔类型数值不能转换为布尔类型,因此
if (1) { ... }
的写法在 Solidity 中 无效 。
ERC
简介
ERC
全称是 “Ethereum Request for Comment
”,表示以太坊
的意见征求稿,ERC
中包含技术和组织等注意事项及标准。这套标准其实不光由以太坊官方
提出,还有一些以太坊
爱好者提出。是以太坊生态系统中被广泛使用的关键标准。
什么叫做代币?
代币可以在以太坊中表示任何东西:
- 彩票卷
- 像美元一样的法定货币
- 在线平台中的信誉积分
- 游戏中一个角色的技能
- 一盎司黄金
- 及更多…
ERC-20 提出了一个同质化代币的标准,换句话说,它们具有一种属性,使得每个代币都与另一个代币(在类型和价值上)完全相同。 例如,一个 ERC-20 代币就像以太币一样,意味着一个代币会并永远会与其他代币一样。
下面举例一些比较知名的基于 ERC-20
协议代币:
Tether (USDT)
Chainlink (LINK)
Binance coin (BNB)
USD coin (USDC)
Wrapped bitcoin (WBTC)
Dai (DAI)
代币信息
一个标准的代币信息需要包括一个下面这些信息:
- 代币名称
name
- 代币标识
symbol
- 代币小数位数
decimals
- 代币的总发行量
totalSupply
- 某地址的代币数量
balance
- 授权代币数量
allowance
如果智能合约实施了下列方法和事件,它可以被称为 ERC-20 代币合约,一旦部署,将负责跟踪以太坊上创建的代币。
方法
function name() public view returns (string)
function symbol() public view returns (string)
function decimals() public view returns (uint8)
function totalSupply() public view returns (uint256)
function balanceOf(address _owner) public view returns (uint256 balance)
function transfer(address _to, uint256 _value) public returns (bool success)
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success)
function approve(address _spender, uint256 _value) public returns (bool success)
function allowance(address _owner, address _spender) public view returns (uint256 remaining)
事件
event Transfer(address indexed _from, address indexed _to, uint256 _value)
event Approval(address indexed _owner, address indexed _spender, uint256 _value)
合约案例
智能合约代码
# Kun.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;
import "./Context.sol";
contract Kun is Context {
/*
0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2
*/
// 1.代币信息
// 代币名称 name
// 代币标识 symbol
// 代币小数位数 decimals
// 代币的总发行量 totalSupply
// 某地址代币数量 balance
// 授权代币数量 allowance
string private _name;
string private _symbol;
uint8 private _decimals;
uint256 private _totalSupply;
mapping(address => uint256) private _balances;
mapping(address => mapping(address => uint256)) private _allowance;
// 2.初始化
constructor() {
_name = "Kun";
_symbol = "KUN";
_decimals = 18;
_mint(_msgSender(), 10000 * 10 * _decimals);
}
event Transfer(address indexed _from, address indexed _to, uint256 _value);
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
// 合约内部函数
function _mint(address account, uint256 amount) internal {
require(account != address(0), "ERC20: mint to the zero addres");
_totalSupply += amount;
unchecked {
_balances[account] += amount;
}
}
function _transfer(
address from,
address to,
uint256 amount
) internal {
require(from != address(0), "ERC20: transfer from the zero address");
require(to != address(0), "ERC20: transfer to the zero address");
uint256 fromBalance = _balances[from];
require(fromBalance >= amount, unicode"ERC20: 余额不足");
unchecked {
_balances[from] = fromBalance - amount;
_balances[to] += amount;
}
emit Transfer(from, to, amount);
}
function _approve(
address owner,
address spender,
uint256 amount
) internal {
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve from the zero address");
_allowance[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
function _spendAllowance(
address owner,
address spender,
uint256 amount
) internal {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance != type(uint256).max) {
require(currentAllowance >= amount, unicode"余额不足");
unchecked {
_approve(owner, spender, currentAllowance - amount);
}
}
}
// 取值器
function name() public view returns (string memory) {
return _name;
}
function symbol() public view returns (string memory) {
return _symbol;
}
function decimals() public view returns (uint8) {
return _decimals;
}
function totalSupply() public view returns (uint256) {
return _totalSupply;
}
// 返回某个账户下有多少钱
function balanceOf(address _owner) public view returns (uint256 balance) {
return _balances[_owner];
}
// 返回授权代币数量
function allowance(address _owner, address _spender)
public
view
returns (uint256 remaining)
{
return _allowance[_owner][_spender];
}
// 代币转发
function transfer(address _to, uint256 _value)
public
returns (bool success)
{
address owner = _msgSender();
_transfer(owner, _to, _value);
return true;
}
/*
主体:借款人,贷款人,中介公司,房屋出售者 account
授权:贷款人(银行) 借钱给我 approve 100w
提款:从银行贷款账户里提钱给自己 tranferFrom(from, to, amount) 1w
支付房款:借款人转账给房屋出售者 tranferFrom 90w
支付佣金:借款人转账给中介公司 tranferFrom 9w
*/
// 代币授权
function approve(address _spender, uint256 _value) public returns (bool) {
// 银行授权给我(银行贷款给我)owner是授权人 _spender被授权人
address owner = _msgSender();
_approve(owner, _spender, _value);
return true;
}
// 授权代币转发
function transferFrom(
address _from,
address _to,
uint256 _value
) public returns (bool) {
address owner = _msgSender();
_spendAllowance(_from, owner, _value); // 修改授权信息
_transfer(_from, _to, _value); // 执行转账
return true;
}
}
# Context.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;
contract Context {
function _msgSender() view internal returns (address){
return msg.sender;
}
}
前端代码
前提:智能合约部署到线上,并且在项目中的 artifacts
中导出合约的 json
放在前端项目里面。
首先创建 Vue3
项目,并且安装 web3
包。
判断小狐狸钱包是否连接
:
if (typeof window.ethereum != 'undefined') {
console.log("小狐狸钱包插件已安装")
}
创建合约对象
:
import Web3 from 'web3';
import kunJSON from './assets/Kun.json'
const geerliWS = "wss://sepolia.infura.io/ws/v3/your apis";
const web3 = new Web3(geerliWS)
const myContract = web3.eth.Contract(kunJSON.abi, "部署的合约地址");
const name = ref("")
const symbol = ref("")
const totalSupply = ref("")
const balanceOf = ref("");
// 获取代币信息
(async () => {
const account = await web3.eth.requestAccounts()// 获取当前连接的地址
name.value = await myContract.methods.name().call();
symbol.value = await myContract.methods.symbol().call();
totalSupply.value = await myContract.methods.totalSupply().call();
balanceOf.value = await myContract.methods.balanceOf(account[0]).call();
})();
// 转账
const send = () => {
const account = web3.utils.toWei("1", "ether")
myContract.methods.transfer("转账地址", account).send({
from: "被转账地址"
}).on("receipt", (receipt) => {
console.log(receipt)
})
}
注意:需要消耗 gas
费的时候,末尾需要携带 .send({...})
方法,而不需要消耗 gas
费的时,则使用 call()
。
openzeppelin 智能合约库
OpenZeppelin
是一个使用以太坊智能合约语言 Solidity
进行构建的开发框架,可以简化 智能合约
和 Dapp
的开发。
OpenZeppelin 合约和库已成为行业标准,其开源代码模板历经了以太坊及其他区块链的实战考验,帮助开发者最大限度降低风险。Openzeppelin 代码包括使用度最高的ERC标准及拓展部署,已被社区在各类指南以及操作教程中大量使用。
Contract Wizard
OpenZeppelin
开发了一种基于网络的线上智能合约交互式工具,它可能是使用 OpenZepplin
代码编写智能合约最简单快捷的方式。这一工具被成为 Contracts Wizard
。
基本使用
首先进入官网,进入到这个页面:
设置完 代币名称
、单位
和 数量
后,暗色区域会自动生成代码,复制代码到自己的项目中即可。
然后在 Remix
调试和部署界面修改虚拟机的环境为你使用的钱包,钱包授权之后,把合约部署支付一定的 以太币
后,在钱包面板中添加代币后,代币则添加成功。