1,智能合约的前世今生
从比特币到以太坊,从简单的脚本到图灵完备的合约,从签名,多签到合约
2,simplestorage合约
从simplestorage入门, 看合约layout。从remix编译和调试结果引出gas, EVM, memory, storage和calldata
pragma solidity >=0.4.16 <0.7.0;
contract SimpleStorage {
uint storedData;
function set(uint x) public {
storedData = x;
}
function get() public view returns (uint) {
return storedData;
}
}
3,solidity和其它编程语言的不同:
区块链共识的一致性特性决定了:
(1)不能使用本机随机数
(2)不能访问IO
区块链的可用性决定了:
(1)资源收费,抵御粉尘攻击 gas 基于数据存储和指令执行收费
(2)运行合约需要沙盒环境 EVM
对照表:
OS -> 区块链
database -> mpt
APP -> 智能合约
多语言互操作:protobuf -> abi
memory -> memory
固化存储: file -> storage
常数: const -> calldata
从gas看设计:
gas花费是solidity代码设计和编译器优化的重点。
storage: 可读可写, 最烧gas,是优化的重点。编译时分配,删除即赋值。
calldata:常数,只读。数据为0, 1 byte消耗4 gas; 数据非零, 1 byte消耗68 gas。合约bytecode和调用数据均属于calldata类型
从gas消耗看调用数据的layout设计,为什么只取function selector的前4个字节。
keccak256("set(uint256)")
60fe47b16ed402aae66ca03d2bfc51478ee897c26a1158669c7058d5f24898f4
只取前4个字节可以节省: 68 * 32 - 68 * 4 = 1904 gas
4,solidity高级语法:继承,接口, 库
继承
// Test Inherit
contract BaseA {
uint256 a;
constructor(uint256 y) internal {
a = y;
}
function set(uint256 x) public {
a = x;
}
}
// expect that constructor() of BaseA contract will execute first and then its constructor()
// The same function will override
contract TestInherit is BaseA {
// explicit specify the param of base contract's constructor
constructor() public BaseA(1) {
a = 2;
}
// explicit call method of base contract
function set(uint256 x) public {
BaseA.set(x);
}
}
基类合约和子类合约编译到一块。部署时只需要部署子类合约。
接口
//
// Test interface call
interface InterfaceA {
function set(uint256 x) external;
}
contract TestA {
uint256 a;
constructor() public {
a = 1;
}
// visibility compatiable: public against external(interface)
function set(uint256 x) public {
a = x;
}
}
contract TestInterfaceA {
constructor(address _addr) public {
InterfaceA testA = InterfaceA(_addr);
testA.set(2);
}
}
首先部署实现接口的合约,将合约地址传入或者硬编码到调用合约。调用合约通过接口即可调用,调用方法和调用基类合约一样。
库
library basicMath {
function add(uint x, uint y) public pure returns (uint z) {
z = x + y;
}
function subtract(uint x, uint y) public pure returns (uint z) {
z = x - y;
}
}
contract TestUseLibrary {
uint256 a;
constructor() public {
a = basicMath.add(1, 2);
}
}
库的调用有2种方式:
1,不需要修改状态(库函数view/pure),直接调用/using for
(1), 库函数是internal函数,直接编入调用合约,通过JUMP跳转调用
(2), 否则,通过DELEGATECALL指令调转到库的地址(需要先部署库合约),从而调用库函数
2,需要修改状态的,通过addr.delegatecall调用
下面的库函数indexOf是view,但不是internal,所以是1.2中的情况
pragma solidity >=0.0.0;
library Search {
function indexOf(uint[] storage self, uint value) public view returns (uint) {
for (uint i = 0; i < self.length; i++)
if (self[i] == value) return i;
return uint(-1);
}
}
编译调用合约
solc.exe --bin consuming-contract.sol
======= consuming-contract.sol:C =======
Binary:
608060405234801561001057600080fd5b50610218806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063e33b87071461003b578063e81cf24c14610069575b600080fd5b6100676004803603602081101561005157600080fd5b81019080803590602001909291905050506100a1565b005b61009f6004803603604081101561007f57600080fd5b8101908080359060200190929190803590602001909291905050506100d0565b005b600081908060018154018082558091505090600182039060005260206000200160009091929091909150555050565b60008073__$501e46c9438942acd821033cbc3fe1b072$__6324fef5c89091856040518363ffffffff1660e01b8152600401808381526020018281526020019250505060206040518083038186803b15801561012b57600080fd5b505af415801561013f573d6000803e3d6000fd5b505050506040513d602081101561015557600080fd5b810190808051906020019092919050505090507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8114156101c15760008290806001815401808255809150509060018203906000526020600020016000909192909190915055506101de565b81600082815481106101cf57fe5b90600052602060002001819055505b50505056fea265627a7a723158206dd50d8c9a5b8f6daf20ab2be9e6446b5fcf427150b19033657f1f148bb7f29d64736f6c63430005110032
// $501e46c9438942acd821033cbc3fe1b072$ -> single-lib.sol:Search
======= single-lib.sol:Search =======
Binary:
610124610026600b82828239805160001a60731461001957fe5b30600052607381538281f3fe730000000000000000000000000000000000000000301460806040526004361060335760003560e01c806324fef5c8146038575b600080fd5b606b60048036036040811015604c57600080fd5b8101908080359060200190929190803590602001909291905050506081565b6040518082815260200191505060405180910390f35b600080600090505b838054905081101560c4578284828154811060a057fe5b9060005260206000200154141560b8578091505060e9565b80806001019150506089565b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff90505b9291505056fea265627a7a72315820f7867af2de0fc9c862047dbea0e2e67ddf478cde90d2f1c59d758d70e2f458d564736f6c63430005110032
上述bytecode中" 501 e 46 c 9438942 a c d 821033 c b c 3 f e 1 b 072 501e46c9438942acd821033cbc3fe1b072 501e46c9438942acd821033cbc3fe1b072"在链接的时候会被替换为库合约的地址,调用库的合约会通过DELEGATECALL指令调转到库的地址,从而调用库函数