Solidity基础备忘

195 阅读3分钟

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,就可以修改了。

图片.png

数据类型

布尔bool,整型int/uint,定长fixed/ufixed,地址address,定长字节数组,变长字节数组,枚举

需要说明的是:

  • 整型支持关键字uint8到uint256,int8到int256。可调配的选择项非常丰富,这对于在资源消耗相对敏感的区块链环境中很重要。

  • 同理,还有定长数组,包括bytes1, bytes2, bytes3, ..., bytes32。

  • 地址类型拥有成员变量balancetransfer。如果你之前没有了解过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;
}

图片.png

外部调用

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;
}

图片.png

可以看出,所消耗的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关键字