abi-to-sol在线转换
https://gnidan.github.io/abi-to-sol/
发布合约
https://ropsten.etherscan.io/verifyContract-solc?a=0x9bbc5c7eeb4ac6facce81aba65fd9a1b8bb28cd0&c=v0.8.1%2bcommit.df193b15&lictype=1
一、数据类型,学一门语言,先看数据类型:
1.基础类型 value type
- bit:位
一个二进制数据0或1代表1bit - byte:字节
存储空间的基本计量单位,如sql server中的varchar(60)即是指60个字节
1 byte = 8 bit - 一个英文字母占一个字节
1 英文字母 = 1 byte = 8 bit - 一个汉字占2个字节
1 汉字 = 2 byte = 16 bit
5.error ,0.8以上版本
2.引用类型 reference type
2.1数组 (字符串与bytes是特殊的数组,所以也是引用类型)
uint[5] array; 固定长度的数字,不能改变长度,不能用push等。
2.2struct (结构体),类似js object,用.访问成员
struct成员是任意type
value type、reference type、mapping都可以
也可以是别的struct、或者dynamic array
struct stu{
uint id;
string name;
mapping(uint=>string) maptest; //mapping即使在结构体内,初始化时也是可以忽略的
}
stu memory student2=stu({name:‘stu2’,id:5678}); // 设置
2.3map (映射)
mappingName[name] = “something”; //赋值
mappingName[name]; //获取值
第三方库:
mapping: github.com/ethereum/dapp-bin/blob/master/library/iterable_mapping.sol
mapping没有长度,无序,所以获取不到长度。不能用迭代访问。
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
contract MappingExample{
mapping(address => uint) public balances;
function update(uint amount) public returns (address addr){
balances[msg.sender] = amount;
return msg.sender;
}
}
数据位置data location类型,分为memory(内存-临时)和storage(区块链-永久),通过在变量名前声明memory还是storage来定义该变量的数据位置。一般来讲,函数参数默认为memory,局部复杂类型(作用域为局部)以及状态变量(作用域为全局)属于storage类型。还有一个calldata与memory差不多,专门用于存储函数参数的,也不是永久存储。额外提一点,EVM的memory是基于stack的,stack可以临时存储一些小的局部变量。这些变量存储消耗的gas是不同的,storage最大>memory(calldata与memory差不多)较小>stack几乎免费
- 数组类型Arrays,长度可定可变,可以存储于storage和memory,元素类型可以是任何类型,但memory时不能是映射类型(就是键值对类型)。uint[5] array; 固定长度的数字,不能改变长度,不能用push等。
- 结构体struct(如下例子),与Go语言相同的设定,自定义类型,使用方式也与Go极为相似。类似js的对象,使用前需要声明。
- mapping 类型就是键值对,现在最新语言都会给自身增加键值对数据结构的封装支持。mapping的声明方式为:
mapping(_KeyType => _ValueType)
键值对中间通过一个“=>”连接。元素内容,Solidity类型均可,与其他键值对使用差不多,遇到问题再深入研究。
3.地址类型 address
地址类型表示以太坊地址,长度为20字节,默认0x40个0。unit160(address)
地址可以使用 .balance方法获得余额,也可以使用 .transfer方法将余额转到另一个地址。
address x = 0x212;
address myAddress = this;
if (x.balance < 10 && myAddress.balance >= 10)
x.transfer(10);
address的四个方法
send,call,callcode,delegatecall
例子:
发送以太币的send方法
//下面是send方法,涉及到以太币的情况可能用到payable,senddemo方法是可以发送以太币过去的,add.transfer(u)
pragma solidity ^0.4.1;
contract addressDemo{
function addressDemo() payable{
}
function sendDemo(address add){
uint u=1 ether;//以太币的最小单位是wei,最大单位是ether
add.transfer(u)
}
}
call方法,注意地址.call,和地址.delegatecall方法的区别,call是自己本身不发生改变,被调用的值发生改变,delegatecall是自己本省的值发生改变,被调用的不发生改变
pragma solidity ^0.4.1;
//首先定义了两个合约
contract A{
uint public p;
event e(address add,uint p)//为了观察声明一个事件
//定义了一个方法
function fun(uint u1,uint u2) {
p=u1+u2;//改变了A合约中的p,改变被调用者合约方法中的变量
e(msg.sender,p)//下面的2,3传给了fun方法
}
}
contract B{
uint public q;
bool public b;
//当我们用B中的call方法的时候,调用A中的某个方法的执行,只会改变A中某个方法的值,B中的某个方法的值不会发生改变,被调用的合约本身发生改变
function call1(address add) returns(bool){
b=add.call(bytes4(keccak256("fun(uint256,uint256)")),2,3)
return b;//下面的add调用call方法
}
//下面的delegatecall是会改变B中的某个方法,而A中的某个方法是不会有任何的改变,下面是自己的合约发生变化
function call2(address add) returns(bool){
b=add.delegatecall(bytes4(keccak256("fun(uint256,uint256)")),1,3)
return b;
}
}
例子
pragma solidity ^0.4.0;
//定义一个合约
contract CA{
uint public p;
bytes public failmsg;
string public str;
event e(address add,uint p)
event e1(address add, bytes b);
//定义一个构造函数,构造方法
function CA(string _str) {
str=_str;//str重新被赋值
}
function fun(uint u1,uint u2) {
p=u1+u2;
e(msg.sender,p)
}
//构造一个匿名函数
function () {//下面就会执行这个匿名函数
failmsg=msg.data;
e1(msg.sender,failmsg)//
}
}
contract CB{
uint public q;
bool public b;
function call1(address add) returns(bool){
b=add.call(bytes4(keccak256("fun(uint256,uint256)")),2,3);
return b
}
function call2(address add) returns(bool){
b=add.delegatecall(bytes4(keccak256("fun(uint256,uint256)")),2,3);
return b
}
//使用的是call方法,上面发生的值发生改变,下面不发生改变
function call3(address add) returns(bool){
b=false
b=add.call("aaaa",2,4,5,54,3);
return b
}
//下面是本身自己的值发生改变,b的值发生改变
function call4(address add) returns(bool){
b=false;
b=add.delegatecall("bbbb",5,"10x2323",43);//执行匿名函数
return b
}
}
二、变量类型:
1.状态变量
2.局部变量
3.全局变量:blockhash(uint blockNumber) returns (bytes32)、block.gaslimit (uint)、tx.gasprice (uint)
pragma solidity ^0.5.0;
contract SolidityTest {
uint storedData; // 状态变量
constructor() public {
storedData = 10; // 使用状态变量
}
}
三、作用域:
Public – 公共状态变量可以在内部访问,也可以通过消息访问。对于公共状态变量,将生成一个自动getter函数。
external-外包合约才能访问,内部访问要加this
Internal – 内部状态变量只能从当前合约或其派生合约内访问。
Private – 私有状态变量只能从当前合约内部访问,派生合约内不能访问。
四、data location, 4个位置:(https://www.lidihuo.com/solidity/solidity-variabledatalocation.html)
Storage
该存储位置存储永久数据,这意味着该数据可以被合约中的所有函数访问。可以把它视为计算机的硬盘数据,所有数据都永久存储。
保存在存储区(Storage)中的变量,以智能合约的状态存储,并且在函数调用之间保持持久性。与其他数据位置相比,存储区数据位置的成本较高。
Memory
内存位置是临时数据,比存储位置便宜。它只能在函数中访问。
通常,内存数据用于保存临时变量,以便在函数执行期间进行计算。一旦函数执行完毕,它的内容就会被丢弃。你可以把它想象成每个单独函数的内存(RAM)。
Calldata
Calldata是不可修改的非持久性数据位置,所有传递给函数的值,都存储在这里。此外,Calldata是外部函数的参数(而不是返回参数)的默认位置。
Stack
堆栈是由EVM (Ethereum虚拟机)维护的非持久性数据。EVM使用堆栈数据位置在执行期间加载变量。堆栈位置最多有1024个级别的限制。
花费gas:storage > memory(calldata) > stack
五、变量的数据位置
规则1 – 状态变量
状态变量总是存储在存储区storage中。(隐式地标记状态变量的位置)
pragma solidity ^0.5.0;
contract DataLocation {
// storage
uint stateVariable;
uint[] stateArray;
uint storage stateVariable; // 错误
uint[] memory stateArray; // 错误
}
规则2 – 函数参数与返回值
函数参数(值类型)包括返回参数(值类型)都存储在内存memory中。
return
pragma solidity ^0.5.0;
contract DataLocation {
// storage
uint stateVariable;
uint[] stateArray;
function calculate(uint num1, uint num2) public pure returns (uint result) {
return num1 + num2
}
}
规则3 – 局部变量
值类型的局部变量存储在内存中。但是,对于引用类型,需要显式地指定数据位置。
pragma solidity ^0.5.0;
contract Locations {
/* 此处都是状态变量 */
// 存储在storage中
bool flag;
uint number;
address account;
function doSomething() public {
/* 此处都是局部变量 */
// 值类型
// 所以它们被存储在内存中
bool flag2;
uint number2;
address account2;
// 引用类型,需要显示指定数据位置,此处指定为内存
uint[] memory localArray;
}
}
不能显式覆盖具有值类型的局部变量。
function doSomething() public {
/* 此处都是局部变量 */
// 值类型
bool memory flag2; // 错误
uint Storage number2; // 错误
address account2;
}
规则4 – 局部storage的赋值,只能赋值给引用类型指针reference
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.8.0;
contract C{
uint[] x;
function f(uint[] memory memoryArray)public{
x = memoryArray; // 会把整个memory array复制进去storage中
uint[] storage y = x; // 把x的reference复制给y
y[7]; //回传x的第八个元素
y.pop(); // 会将x的最后一个元素删除
delete x; // 把x清空
// 下面2行会失败,要赋值指针,x就是指针
// y = memoryArray;
// delete y;
g(x); // 只会传达reference
h(x); // 会产生一个独立,暂时的复制,在memory中
}
function g(uint[] storage) internal pure{} // 因为只是修改指针,所以消耗gas少,便宜
function h(uint[] memory) public pure{}// 全部复制一个新的对象,所以消耗gas多,贵。
}
规则5 – 外部函数的参数
Calldata是不可修改的非持久性数据位置,所有传递给函数的值,都存储在这里。此外,Calldata是外部函数的参数(而不是返回参数)的默认位置。
calldata
web3.sendTransaction(from,to,calldata)
calldata就是外部函数的参数
六、赋值数据的位置
七函数
状态可变性(mutability)
Private(私有):限制性最强,函数只能在所定义的智能合约内部调用。
Internal(内部):可以在所定义智能合约内部调用该函数,也可以从继承合约中调用该函数。
External(外部):只能从智能合约外部调用。 (如果要从智能合约中调用它,则必须使用 this。)
Public(公开):可以从任何地方调用。 (最宽松)
状态可变性(mutability)
view:用view声明的函数只能读取状态,而不能修改状态。不消化gas
pure:用pure声明的函数既不能读取也不能修改状态。不消化gas
payable:用payable声明的函数可以接受发送给合约的以太币,如果未指定,该函数将自动拒绝所有发送给它的以太币。
pragma solidity 0.8.10;
contract C {
uint balance = 0;
// Payable function that allows other contracts
// to send ether to this contract.
function deposit () payable public{
balance += msg.value;
}
}
no-payable 如果未指定,该函数将自动拒绝所有发送给它的以太币。
pragma solidity 0.8.10;
contract D { uint count = 0;
// non-payable function that reads and modifies state.
function increment() public returns(uint){
count += 1;
return count;
}
}
增删改查例子:
// SPDX-License-Identifier: MIT
// 可见性:public 状态可变性:空
// 此函数将用户的ID删除,如果找到,则将其从数组中删除;如果用户不存在,则回退交易。
// 提示:由于最后三个函数都需要查找用户,因此你将需要创建一个私有函数,该函数将获取用户的ID并在数组中返回其索引(如果找到),以避免重复相同的代码。
pragma solidity ^0.7.0;
contract Crud {
struct User {
uint256 id;
string name;
}
User[] public users;
uint256 public nextId = 1;
function add(string memory name) public {
User memory user = User({id : nextId, name : name});
users.push(user);
nextId++;
}
function read(uint256 id) public view returns(string memory){
uint256 i = find(id);
return users[i].name;
}
function update(uint256 id, string memory newName) public {
uint256 i = find(id);
users[i].name = newName;
}
function destroy(uint256 id) public {
uint256 i = find(id);
delete users[i];
}
function find(uint256 id) private view returns(uint256){
for(uint256 i = 0; i< users.length; i++) {
if(users[i].id == id)
return i;
}
revert("User not found");
}
}
二、Solidity 0.4.10 版本发布了新的 assert() , require() 和 revert() 函数,解决了以前代码中有困惑的地方。
参考:https://blog.csdn.net/qq_33829547/article/details/80377689
https://remix.ethereum.org/#optimize=false&runs=200&evmVersion=null&version=soljson-v0.7.1+commit.f4a555be.js
Solidity 0.4.10之前(以及其后一段时间),这种强制授权处理方式很普遍:
if (msg.sender != owner) { throw; }
完全等价于如下三种形式(新方法):
if(msg.sender != owner) { revert(‘Something bad happened’); }
assert(msg.sender == owner);
require(msg.sender == owner, ‘Something bad happened’);// 参数1不成立,执行参数2. 如果参数1成立,执行require
注意在 assert() 和 require() 例子中的条件声明,是 if 例子中条件块取反,也就是用 ==代替了 != 。
revert | require | assert |
---|---|---|
代替throw | – | – |
允许返回一个数值 | 允许返回一个数值 | |
将剩余gas返还调用者 | 将剩余gas返还调用者 | 不返还gas |
if后面使用 | 在函数最开始的地方使用 | 在函数结尾处使用 |
一般地,尽量使用 require 函数,require 应该在函数最开始的地方使用 |
一般地,尽量少使用 assert 调用,assert 应该在函数结尾处使用
除非认为之前的检查(用 if 或 require )会导致无法验证 overflow,否则不应该盲目使用 assert 来检查 overflow”
一、程序的授权码// SPDX-License-Identifier: GPL-3.0
可以从这里挑:https://spdx.org/licenses/
不开发直接写:// SPDX-License-Identifier: UNLICENSED
二、版本:pragma solidity =0.8.7; //合约版本
三、合约
contract Vote {
…
}