Solidity是一门专为实现智能合约而创建的高级编程语言,在以太坊虚拟机Ethereum Virtual Machine (EVM)上运行。它与C++,Python以及JavaScript颇有渊源。
比较重要的特性:
- 提供payable等关键字,在语言层面支持支付
- 提供address数据类型,用于定位用户和合约账号
- 使用区块链进行数据存储。任何数据的每一个状态都可以永久存储,因此使用时需要注意确定变量是使用内存还是使用区块链存储
- 一旦出现异常,所有的执行都会被撤销,以确保合约执行的原子性,保证数据一致性
一个简单的例子
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract BasicTest {
uint myStoredData;
function myGet() public view returns (uint) {
return myStoredData;
}
function mySet(uint what) public {
myStoredData = what;
}
}
代码编译部署到区块链上之后,任何人只要知道了该合约的地址及函数签名,都可以修改myStoredData数据。比如,该合约地址为 0xaE036c65C649172b43ef7156b009c6221B596B8b 那么编写如下代码:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
interface BasicTest {
function myGet() external view returns (uint);
function mySet(uint w) external;
}
contract Super {
BasicTest bt;
function setAddr(BasicTest addr) public {
bt = addr;
}
function test(uint w) public returns (uint) {
bt.mySet(w);
return bt.myGet();
}
}
输入参数为合约地址,执行setAddr;然后输入欲改值1356,就可以修改了。
数据类型
布尔bool,整型int/uint,定长fixed/ufixed,地址address,定长字节数组,变长字节数组,枚举
需要说明的是:
-
整型支持关键字uint8到uint256,int8到int256。可调配的选择项非常丰富,这对于在资源消耗相对敏感的区块链环境中很重要。
-
同理,还有定长数组,包括bytes1, bytes2, bytes3, ..., bytes32。
-
地址类型拥有成员变量
balance和transfer。如果你之前没有了解过Solidity, 是不是觉得这个特性相当amazing ;) 她的确就是的,这给交易提供了天然的支持。
函数
function (<parameter types>) {internal|external} [pure|constant|view|payable] [returns (<return types>)]
函数的可见性
可指定为external, public, internal, 或者private
对于public和private,相信学过其他高级语言的人都能明白:
-
public修饰的变量和函数,任何用户或者合约都能调用和访问。
-
private修饰的变量和函数,只能在其所在的合约中调用和访问,即使是其子合约也没有权限访问。
-
internal 和 private 类似,不过, 如果某个合约继承自其父合约,这个合约即可以访问父合约中定义的“内部”函数。
-
external 与public 类似,只不过这些函数只能在合约之外调用 - 它们不能被合约内的其他函数调用。
函数的状态可变性
- pure:纯函数,不允许修改或访问状态
- view:不允许修改状态
- payable:允许从消息调用中接收以太币Ether
- constant:与view相同,一般只修饰状态变量,不允许赋值(除初始化以外)
如果你试图在一个用view或者pure修饰的函数体内修改变量,或者在一个未使用payable修饰的函数体内发送ether,那么编译的时候,就会给出错误。
内部函数调用
仅仅发生在一个合约内部。可以理解为“方法调用”。
外部函数调用
外部函数调用使用消息机制实现。外部调用事件消息包括了发送者,接收者,内容,ether数量,以及gas。
一个合约调用其它合约的函数,只有通过外部函数调用的方式,没有其它方式。
调用一个public函数,分别使用内部调用和外部调用,对比一下:
内部调用
function g1(uint a) public returns(uint ret) {
return 44;
}
function y2() public returns(uint ret) {
uint a = g1(100);
for(uint i=0; i < 30; i++) {
g1(100);
}
return a;
}
外部调用
function g1(uint a) public returns(uint ret) {
return 44;
}
function y1() public returns(uint ret) {
uint a = this.g1(100);
for(uint i=0; i < 30; i++) {
this.g1(100);
}
return a;
}
可以看出,所消耗的gas差别很大。
进一步试验
contract FunctionTest{
uint public v1;
uint public v2;
function internalFunc() internal{
v1 = 10;
}
function externalFunc() external returns (uint res){
v2 = 20;
return v2;
}
function resetV2() public {
v2 = 0;
}
function callFunc() public {
//直接使用内部的方式调用
internalFunc(); //<--- 合约内部直接调用,正确
//不能在内部调用一个外部函数,会报编译错误。
//Error: Undeclared identifier.
//externalFunc(); //<--- 外部合约不可以调用
//不能通过`external`的方式调用一个`internal`
//Member "internalFunc" not found or not visible after argument-dependent lookup in contract FuntionTest
//this.internalFunc(); //<--- this.相当于外部调用
//使用`this`以`external`的方式调用一个外部函数
this.externalFunc();
}
}
// 合约的继承
contract Son is FunctionTest {
function callInternalFunc() public{
internalFunc(); // 可以继承父合约的内部方法
this.externalFunc();
}
}
contract FunctionTest1{
uint public v3;
function externalCall(FunctionTest ft) public {
//调用另一个合约的外部函数
v3 = ft.externalFunc();
//不能调用另一个合约的内部函数
//Error: Member "internalFunc" not found or not visible after argument-dependent lookup in contract FuntionTest
//ft.internalFunc();
}
function resetV3() public {
v3 = 0;
}
}
最后,再看一个发送ether的例子。可以指定gas。具体如何估算,留个悬念,请见下回分解吧。
function g(uint a) public payable returns(uint ret) {
return 44;
}
function y() public payable returns(uint ret) {
uint a = this.g{value:11,gas:5800}(100);
return a;
}
constructor() payable{} //必不可少
注意: payable关键字