smilejiasmile 2019-12-27
一个智能合约是一套以数字形式定义的承诺(promises) ,包括合约参与方可以在上面执行这些承诺的协议。一个合约由一组代码(合约的函数)和数据(合约的状态)组成,并且运行在以太坊虚拟机上.
以太坊虚拟机(EVM)使用了256比特长度的机器码,是一种基于堆栈的虚拟机,用于执行以太坊智能合约 。由于EVM是针对以太坊体系设计的,因此使用了以太坊账户模型(Account Model)进行价值传输。
读取交易数据。 读取或写入合约自己的存储空间。 读取环境变量(块高,哈希值,gas) 向另一个合约发送一个“内部交易”。
Solidity是一种智能合约高级语言,运行在Ethereum虚拟机(EVM)之上。
solidity 语言特点
它的语法接近于Javascript,是一种面向对象的语言。但作为一种真正意义上运行在网络上的去中心合约,它有很多的不同点:
合约开发步骤:
1. 写合约 2. 编译合约 3. 部署合约 4. 测试合约 5. 生成java文件
说明:WeBase
帮助用户开发、测试和生成对应的Java类,用户获取java类,可以直接进行业务开发,加快开发进度和效率。
pragma solidity ^0.4.24; contract HelloWorld { string name; function HelloWorld() { name = "Hello, World!"; } function get()constant returns(string) { return name; } function set(string n) { name = n; } }
下载应用脚手架
$ git clone https://github.com/FISCO-BCOS/spring-boot-starter.git
?
[更多工具和例子]()
address:以太坊地址的长度,大小20个字节,160位,所以可以用一个uint160编码。地址是所有合约的基础,所有的合约都会继承地址对象,也可以随时将一个地址串,得到对应的代码进行调用。合约的地址是基于账号随机数和交易数据的哈希计算出来的
ABI:是以太坊的一种合约间调用时或消息发送时的一个消息格式。就是定义操作函数签名,参数编码,返回结果编码等。
交易:以太坊中“交易”是指存储从外部账户发出的消息的签名数据包。
简单理解是:只要对区块链进行写操作,一定会发生交易。
交易回执:发生交易后的返回值
版本声明
pragma solidity ^0.4.24;
状态变量(State Variables)
string name;
详细说明见下文
函数(Functions)
function get()constant returns(string) { return name; } function set(string n) { name = n; }
事件(Events)
//事件的声明 event AddMsg(address indexed sender, bytes32 msg); //事件的使用 function setData(int256 x) public { storedData = x; AddMsg(msg.sender, "in the set() method"); }
结构类型(Structs Types)
contract Contract { struct Data { uint deadline; uint amount; } Data data; function set(uint id, uint deadline, uint amount) { data.deadline = deadline; data.amount = amount; } }
函数修饰符(Function Modifiers)
类似于hook
modifier only_with_at_least(int x) { if (x >= 5) { x = x+10; _; } }
面向条件的编程(COP)是面向合约编程的一个子域,作为一种面向函数和命令式编程的混合模式。COP解决了这个问题,通过需要程序员显示地枚举所有的条件。逻辑变得扁平,没有条件的状态变化。条件片段可以被正确的文档化,复用,可以根据需求和实现来推断。重要的是,COP在编程中把预先条件当作为一等公民。这样的模式规范能保证合约的安全。
例子:
contract Token { // The balance of everyone mapping (address => uint) public balances; // Constructor - we‘re a millionaire! function Token() { balances[msg.sender] = 1000000; } // Transfer `_amount` tokens of ours to `_dest`. function transfer(uint _amount, address _dest) { balances[msg.sender] -= _amount; balances[_dest] += _amount; } }
改进后:
function transfer(uint _amount, address _dest) { if (balances[msg.sender] < _amount) return; balances[msg.sender] -= _amount; balances[_dest] += _amount; }
COP的风格
modifier only_with_at_least(uint x) { if (balances[msg.sender] >= x) _; } function transfer(uint _amount, address _dest) only_with_at_least(_amount) { balances[msg.sender] -= _amount; balances[_dest] += _amount; }
!逻辑非&& 逻辑与|| 逻辑或== 等于!= 不等于
更多详情见官方API
变量的存储位置属性。有三种类型,memory,storage和calldata。
Storage - 状态变量的存储模型
大小固定的变量(除了映射,变长数组以外的所有类型)在存储(storage)中是依次连续从位置0开始排列的。如果多个变量占用的大小少于32字节,会尽可能的打包到单个storage槽位里,具体规则如下:
为了方便EVM进行优化,尝试有意识排序storage的变量和结构体的成员,从而让他们能打包得更紧密。比如,按这样的顺序定义,uint128, uint128, uint256,而不是uint128, uint256, uint128。因为后一种会占用三个槽位。
Memory - 内存变量的布局(Layout in Memory)
Solidity预留了3个32字节大小的槽位:
0-64:哈希方法的暂存空间(scratch space)
64-96:当前已分配内存大小(也称空闲内存指针(free memory pointer))
暂存空间可在语句之间使用(如在内联编译时使用)
Solidity总是在空闲内存指针所在位置创建一个新对象,且对应的内存永远不会被释放(也许未来会改变这种做法)。
有一些在Solidity中的操作需要超过64字节的临时空间,这样就会超过预留的暂存空间。他们就将会分配到空闲内存指针所在的地方,但由于他们自身的特点,生命周期相对较短,且指针本身不能更新,内存也许会,也许不会被清零(zerod out)。因此,大家不应该认为空闲的内存一定已经是清零(zeroed out)的。
例子
以太坊地址的长度,大小20个字节,160位,所以可以用一个uint160编码。地址是所有合约的基础,所有的合约都会继承地址对象,也可以随时将一个地址串,得到对应的代码进行调用
event AddMsg(address indexed sender, bytes32 msg);
事件是使用EVM日志内置功能的方便工具,在DAPP的接口中,它可以反过来调用Javascript的监听事件的回调。
var event = instance.AddMsg({}, function(error, result) { if (!error) { var msg = "AddMsg: " + utils.hex2a(result.args.msg) + " from " console.log(msg); return; } else { console.log(‘it error‘) } });
数组是定长或者是变长数组。有length属性,表示当前的数组长度。
一般使用定长的 bytes1~bytes32。在知道字符串长度的情况下,指定长度时,更加节省空间。
new uint[] memory a = new uint[](7);
例子
pragma solidity ^0.4.0; contract SimpleStartDemo{ uint[] stateVar; function f(){ //定义一个变长数组 uint[] memory memVar; //不能在使用new初始化以前使用 //VM Exception: invalid opcode //memVar [0] = 100; //通过new初始化一个memory的变长数组 memVar = new uint[](2); //不能在使用new初始化以前使用 //VM Exception: invalid opcode //stateVar[0] = 1; //通过new初始化一个storage的变长数组 stateVar = new uint[](2); stateVar[0] = 1; } }
length属性
storage变长数组是可以修改length
memory变长数组是不可以修改length
push方法
storage变长数组可以使用push方法
bytes可以使用push方法
pragma solidity ^0.4.2; contract SimpleStartDemo { uint[] stateVar; function f() returns (uint){ //在元素初始化前使用 stateVar.push(1); stateVar = new uint[](1); stateVar[0] = 0; //自动扩充长度 uint pusharr = stateVar.push(1); uint len = stateVar.length; //不支持memory //Member "push" is not available in uint256[] memory outside of storage. //uint[] memory memVar = new uint[](1); //memVar.push(1); return len; } }
下标:和其他语言类似
Memory数组是不能修改修改数组大小的属性
例子
pragma solidity ^0.4.2;
contract SimpleStartDemo {
function f() { //创建一个memory的数组 uint[] memory a = new uint[](7); //不能修改长度 //Error: Expression has to be an lvalue. //a.length = 100; } //storage uint[] b; function g(){ b = new uint[](7); //可以修改storage的数组 b.length = 10; b[9] = 100; }
}
EVM的限制
由于EVM的限制,不能通过外部函数直接返回动态数组和多维数组
//Data层数据 struct Rate { int key1; int unit; uint[3] exDataArr; bytes32[3] exDataStr; } mapping(int =>Rate) Rates; function getRate(int key1) public constant returns(int,uint[3],bytes32[3]) { uint[3] memory exDataInt = Rates[key1].exDataArr; bytes32[3] memory exDataStr = Rates[key1].exDataStr; return (Rates[key1].unit,exDataInt,exDataStr); }
业务场景
function (<parameter types>) {internal(默认)|external} constant [returns (<return types>)]
pragma solidity ^0.4.5; contract FuntionTest{ function internalFunc() internal{} function externalFunc() external{} function callFunc(){ //直接使用内部的方式调用 internalFunc(); //不能在内部调用一个外部函数,会报编译错误。 //Error: Undeclared identifier. //externalFunc(); //不能通过`external`的方式调用一个`internal` //Member "internalFunc" not found or not visible after argument-dependent lookup in contract FuntionTest //this.internalFunc(); //使用`this`以`external`的方式调用一个外部函数 this.externalFunc(); } } contract FunctionTest1{ function externalCall(FuntionTest ft){ //调用另一个合约的外部函数 ft.externalFunc(); //不能调用另一个合约的内部函数 //Error: Member "internalFunc" not found or not visible after argument-dependent lookup in contract FuntionTest //ft.internalFunc(); } }
访问函数有外部(external)可见性。如果通过内部(internal)的方式访问,比如直接访问,你可以直接把它当一个变量进行使用,但如果使用外部(external)的方式来访问,如通过this.,那么它必须通过函数的方式来调用。
pragma solidity ^0.4.2; contract SimpleStartDemo { uint public c = 10; function accessInternal() returns (uint){ return c; } function accessExternal() returns (uint){ return this.c(); } }
修改器(Modifiers)可以用来轻易的改变一个函数的行为。比如用于在函数执行前检查某种前置条件。修改器是一种合约属性,可被继承,同时还可被派生的合约重写(override)
pragma solidity ^0.4.2; contract SimpleStartDemo { int256 storedData; event AddMsg(address indexed sender, bytes32 msg); modifier only_with_at_least(int x) { if (x >= 5) { x = x+10; _; } } function setData(int256 x) public only_with_at_least(x){ storedData = x; AddMsg(msg.sender, "[in the set() method]"); } }
函数也可被声明为常量,这类函数将承诺自己不修改区块链上任何状态。
一般从链上获取数据时,get函数都会加上constant
Solidity通过复制包括多态的代码来支持多重继承。
父类
pragma solidity ^0.4.4; contract Meta { string public name; string public abi; address metaAddress; function Meta(string n,string a){ name=n; abi=a; } function getMeta()public constant returns(string,string,address){ return (name,abi,metaAddress); } function setMetaAddress(address meta) public { metaAddress=meta; } }
子类
pragma solidity ^0.4.4; import "Meta.sol"; contract Demo is Meta{ bytes32 public orgID; function Demo (string n,string abi,bytes32 id) Meta(n,abi) { orgID = id; } }
基于EVM的限制,不能通过外部函数返回动态的内容
合约的架构分两层数据合约和逻辑合约,方便后期合约的升级。更多详情,请参见浅谈以太坊智能合约的设计模式与升级方法 。