智能合约语法

// 转账合约
pragma solidity ^0.8.25;

contract Asset {

    // 注册事件
    event RegisterEvent(string result,string name,uint256 balance);
    // 交易事件
    event TransactionEvent(string result,string from,string to,uint256 account);


    // 账户数据:键为账户名,值为整型数组,第一个为状态值,第二个为余额
    mapping (string => uint256[]) public bank;

    // 注册函数
    function register(string memory name,uint256 balance) public{
        // 判断是否已经注册
        uint256[] memory registerinfo = bank[name];
        if(registerinfo.length == 0){
            bank[name] = [1,balance];
            emit RegisterEvent("success", name, balance);
        }else {
            emit RegisterEvent("fail",name,uint256(0));
        }
    }

    // 查询
    function query(string memory name) public view returns(uint256){
        uint256[] memory userInfo = bank[name];
        if(userInfo.length == 0){
            return uint256(0);
        }
        return userInfo[1];
    }

    // 交易
    function transaction(string memory from,string memory to,uint256 account) public {
        // 验证账户是否存在
        if(bank[from].length == 0 || bank[to].length == 0){
            emit TransactionEvent("account does not exit",from,to,account);
            return ;
        }
        // 验证转账用户余额是否充足
        if(bank[from][1] < account){
            emit TransactionEvent("balance is not enough", from, to, account);
            return ;
        }
        // 转账
        bank[from][1] = bank[from][1] - account;
        bank[to][1] = bank[to][1] + account;
        emit TransactionEvent("success", from, to, account);

    }

}

代码结构

版本声明

指定智能合约的编译器版本,有如下两种表示方法:

// 表示编译器版本介于0.7.0到0.9.0之间
pragma solidity >=0.7.0 <0.9.0;

// 表示使用主版本号为4的编译器的最新版本,但是也可以使用主版本号为4且在0.4.19之后的编译器
pragma solidity ^0.4.19;

合约代码

基本的智能合约应该包含合约声明、构造函数和函数

数据结构

  • 整形:unit:无符号整型,int:有符号整型,还可以通过后面带数字的形式指定存储空间的大小,避免执行合约时多余的空间消耗,unit8,unit16,int8,int16等
  • 布尔类型:bool a = true,bool b = false;
  • 地址类型:可以记录合约地址或外部账户地址,address a = 0xcb76b7d9a3458ef540ade6068dfe2f44e8ff745f,地址可以是一个值,也可以有成员变量,比如address addre = xxxxx;unit256 b = addre.balance。所以地址可以作为一个合约的基础。 
  • 文本数据:string
  • 字符:byte,表示单个字符
  • 字符数组:bytes,不跟数字则表示动态数组,同理可以在后面跟数字限定存储空间的大小,如bytes32,但是此时是指一个字符数组。
  • 结构体:
    struct Person {
        string name;
        uint age;
    }
  • 数组:动态数组,type[],静态数组,type[size]
  • 键值对:当映射的值为uint时,如果键不存在则返回0,如果是数组类型时,如果键不存在则返回长度为0的数组
        // 账户数据
        mapping (string => uint256[]) public bank;
    
        // 注册函数
        function register(string calldata name,uint256 balance) public returns(uint256[] memory){
            bank["h"] = [1,23];
            return bank["h"];
        }

     

指定变量数据位置

solidity中,所有引用类型(字符串、数组等)都需要指定数据位置

  • memory:内存,变量动态分配,有作用域,出了作用域就等待回收
  • storage:区块链存储,在状态变量存储的位置
  • calldata:只读变量,用于函数参数修饰,0.6.9之前只用于外部函数,0.6.9之后可用于任意函数,不可改,就以为不需要存储拷贝,不分配空间

 控制结构

if语句

if(true){
  // }
else{
  // }

for语句

for(unit i = 0; i < 100; i++){
    //
}

while语句

while(true){
    //
}

do while语句

do{
    //
}while(true);

函数

function add(uint a, uint b) public pure returns (uint) {
    return a + b;
}

// 调用
uint result = add(3, 5);

函数可见性

  • public:内外部合约可见
  • private:仅内部可见,合约继承者也不可见
  • external:仅外部可见,内部不可见
  • internal:内部可见,外部不可见

函数操作权限

  • pure:纯函数,不可读取,不可写入
  • view:可读取,不可写入

支付payable

通过payable修饰符声明函数可以接收gas

  • 使用payable的函数说明调用函数者发送的合约将被存储到合约的余额中
  • 如果没有使用payable声明,如果尝试向该合约发送gas,则会导致一个失败的交易

抽象与继承

一个函数中定义了没有函数体的函数,则它是抽象合约;

一个函数如果继承自抽象合约,却没有实现抽象函数,则它也是抽象合约

// 版本声明
pragma solidity ^0.8.0;

// 合约声明
abstract contract Parent {
    function p() public pure  virtual returns(uint);
}

contract Son is Parent{
    function p() public pure override returns( uint) {
        return 12;
    }
}

调用外部函数

contract ExternalContract {
    function externalFunction() public pure returns (uint) {
        return 42;
    }
}

contract MyContract {
    ExternalContract public externalContract;

    constructor(address externalContractAddress) {
        externalContract = ExternalContract(externalContractAddress);
    }

    function interactWithExternal() public view returns (uint) {
        return externalContract.externalFunction();
    }
}

 

事件或日志

 事件定义

  • 事件和日志的使用基本一样,都是通过event定义,通过emit触发
  • 日志偏向于记录信息,事件偏向于传递信息,触发某些操作
  • 事件在他被使用的位置向外界传递你需要传递的参数,外界接收参数的同时可以确定,代码运行到了事件执行位置
  • 事件记录的数据结构花费很小
  • 事件的记录智能合约不可访问
  • 事件存储在交易的日志部分,但是不可以被智能合约直接访问,只能够验证区块链中是否存在该日志
  • 事件最多可以有三个索引,有时候也叫主题,通过indexed修饰,被定义为索引的变量,可以成为后续检索该事件的索引

事件使用

pragma solidity ^0.8.0;

contract EventExample {
    event NewUser(address user, string name);

    function addUser(string memory name) public {
        emit NewUser(msg.sender, name);
    }
}

事件监听

下述两种监听代码来自别的博客

counter = web3.eth.contract(abi).at(address);

counter.Increment(function (err, result) {//开始监听
    if (err) {
        return error(err);
    }
    log("Count was incremented by address: " + result.args.who);
});
var abi = /* abi 通过编译器生成 */;
var ClientReceipt = web3.eth.contract(abi);
var clientReceipt = ClientReceipt.at("0x1234...ab67" /* 地址 */);

var event = clientReceipt.Deposit();

// 观察变化
event.watch(function(error, result){
    //结果将包含`Deposit`调用给定的参数等各种信息
    if (!error)
        console.log(result);
});

// 或通过回调立即开始观察,事件是使用EVM日志内置功能的方便工具,在dapp的接口中,它可以反过来调用Javascript的监听事件的回调。
var event = clientReceipt.Deposit(function(error, result) {
    if (!error)
        console.log(result);
});

事件数据结构

事件外发的数据结构

 支付

下述是一个具备转账功能的合约

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract Payable  {
    // payable 标记函数:接收以太币,接收以后存储到合约中
    function deposit1() external payable {
        // 处理接收到的以太币
    }

    function deposit2() external {}

    // payable 标记函数
    function withdraw() external {
        // msg.sender合约的调用者
        // 把当前合约的所有余额全部转到调用者
        payable(msg.sender).transfer(address(this).balance);
    }

    // 通过balance属性来查看余额
    function getBalance() external view returns (uint256) {
        return address(this).balance;
    }
}

刚才写的合约的deposit1是payable,可以接受用户的转账:

账号拥有99.9999个以太币

3.2. 点击deposit1转账:转账以后,账户少了10个ether,剩下89.99999个以太币了

3.3. 点击getBalance查看当前合约的额度:当前合约的资产刚好是10个ether

3.4. 点击withdraw把当前合约的所有余额全部转到调用者:账号当前的金额又变成99.99999了,当然有损失一些gas费,先忽略

3.5. 再次查看合约拥有的以太币:由于把当前所有的钱都转给操作者了,所以金额变成0了

异常

 异常捕获

function divide(uint256 numerator, uint256 denominator) public returns (uint256) {
    uint256 result;
    try this.division(numerator, denominator) returns (uint256 quotient) {
        result = quotient;
    } catch {
        result = 0;
    }
    return result;
}

异常抛出

异常抛出可以使用require、revert、assert

区别:

  • require:可以检查错误并抛出,相当于revert和if语句的合体,发生异常后会退还剩余gas,消耗小,推荐使用
  • revert:不支持检查,直接抛出,抛出后退还gas,实际上require相当于检查错误后执行了revert
  • assert:断言,检查到不符合条件后直接阻断代码,抛出异常,并且不退换剩余gas,消耗大,不建议在生产环境使用
uint public num=0;
 
function testRevert() public {
num++;
if (num>3){
revert(“revert返回的错误”);
}
num++;
 
}
 
function testAssert() public {
num++;
assert(num<13);
num++;
 
}
 
function testRequire()public {
num++
require(    num<23,”require报错信息”);
num++;
 
}
总结:不管是哪个回退,都是回退到不满足条件前满足条件的状态

 

智能合约demo

solidity

fisco bcos

Asset.sol,需要引用Table.sol,Table.sol中声明了接口,其中的合约实现由Fisco Bcos底层实现

// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.4.25;

import "./Table.sol";

contract Asset {
    // event
    event RegisterEvent(
        int256 ret,
        string indexed account,
        uint256 indexed asset_value
    );
    event TransferEvent(
        int256 ret,
        string indexed from_account,
        string indexed to_account,
        uint256 indexed amount
    );

    KVTable kvTable;
    KVTableFactory tableFactory;
    string constant tableName = "t_asset";

    constructor() public {
        // 构造函数中创建t_asset表
        tableFactory = KVTableFactory(0x1002);

        // 资产管理表, key : account, field : asset_value
        // |  资产账户(主键)      |     资产金额       |
        // |-------------------- |-------------------|
        // |        account      |    asset_value    |
        // |---------------------|-------------------|
        //

        // create table
        tableFactory.createTable(tableName, "account", "asset_value");

        // get table address
        kvTable = tableFactory.openTable(tableName);
    }

    /*
    描述 : 根据资产账户查询资产金额
    参数 :
            account : 资产账户

    返回值:
            参数一: 成功返回0, 账户不存在返回-1
            参数二: 第一个参数为0时有效,资产金额
    */
    function select(string memory account) public view returns (bool, uint256) {
        // 查询
        bool result;
        Entry entry;
        (result, entry) = kvTable.get(account);
        uint256 asset_value = 0;

        if(result){
            asset_value = uint256(entry.getInt("asset_value"));
        }
        return (result, asset_value);
    }

    /*
    描述 : 资产注册
    参数 :
            account : 资产账户
            amount  : 资产金额
    返回值:
            0  资产注册成功
            -1 资产账户已存在
            -2 其他错误
    */
    function register(string memory account, uint256 asset_value)
    public
    returns (int256)
    {
        int256 ret_code = 0;
        bool ret = true;
        uint256 temp_asset_value = 0;
        // 查询账号是否存在
        (ret, temp_asset_value) = select(account);
        if (ret != true) {
            // 不存在,创建
            string memory asset_value_str = uint2str(asset_value);

            // 插入
            Entry entry = kvTable.newEntry();
            entry.set("asset_value",asset_value_str);
            int256 count = kvTable.set(account, entry);
            if (count == 1) {
                // 成功
                ret_code = 0;
            } else {
                // 失败? 无权限或者其他错误
                ret_code = - 2;
            }
        } else {
            // 账户已存在
            ret_code = - 1;
        }

        emit RegisterEvent(ret_code, account, asset_value);

        return ret_code;
    }

    /*
    描述 : 资产转移
    参数 :
            from_account : 转移资产账户
            to_account : 接收资产账户
            amount : 转移金额
    返回值:
            0  资产转移成功
            -1 转移资产账户不存在
            -2 接收资产账户不存在
            -3 金额不足
            -4 金额溢出
            -5 其他错误
    */
    function transfer(
        string memory from_account,
        string memory to_account,
        uint256 amount
    ) public returns (int16) {
        // 查询转移资产账户信息
        bool ret = true;
        uint256 from_asset_value = 0;
        uint256 to_asset_value = 0;

        // 转移账户是否存在?
        (ret, from_asset_value) = select(from_account);
        if (ret != true) {
            // 转移账户不存在
            emit TransferEvent(- 1, from_account, to_account, amount);
            return - 1;
        }

        // 接受账户是否存在?
        (ret, to_asset_value) = select(to_account);
        if (ret != true) {
            // 接收资产的账户不存在
            emit TransferEvent(- 2, from_account, to_account, amount);
            return - 2;
        }

        if (from_asset_value < amount) {
            // 转移资产的账户金额不足
            emit TransferEvent(- 3, from_account, to_account, amount);
            return - 3;
        }

        if (to_asset_value + amount < to_asset_value) {
            // 接收账户金额溢出
            emit TransferEvent(- 4, from_account, to_account, amount);
            return - 4;
        }

        string memory f_new_value_str = uint2str(from_asset_value - amount);

        // 更新转账账户
        Entry entry = kvTable.newEntry();
        entry.set("asset_value",f_new_value_str);
        int256 count = kvTable.set(from_account, entry);
        if (count != 1) {
            // 失败? 无权限或者其他错误?
            emit TransferEvent(- 5, from_account, to_account, amount);
            return - 5;
        }

        string memory to_new_value_str = uint2str(to_asset_value + amount);

        // 更新接收账户
        entry.set("asset_value",to_new_value_str);
        kvTable.set(to_account, entry);

        emit TransferEvent(0, from_account, to_account, amount);

        return 0;
    }

    function uint2str(uint256 _i)
    internal
    pure
    returns (string memory _uintAsString)
    {
        if (_i == 0) {
            return "0";
        }
        uint j = _i;
        uint len;
        while (j != 0) {
            len++;
            j /= 10;
        }
        bytes memory bstr = new bytes(len);
        uint k = len;
        while (_i != 0) {
            k = k-1;
            uint8 temp = (48 + uint8(_i - _i / 10 * 10));
            bytes1 b1 = bytes1(temp);
            bstr[k] = b1;
            _i /= 10;
        }
        return string(bstr);
    }

    function safeParseInt(string memory _a)
    internal
    pure
    returns (uint256 _parsedInt)
    {
        return safeParseInt(_a, 0);
    }

    function safeParseInt(string memory _a, uint256 _b)
    internal
    pure
    returns (uint256 _parsedInt)
    {
        bytes memory bresult = bytes(_a);
        uint256 mint = 0;
        bool decimals = false;
        for (uint256 i = 0; i < bresult.length; i++) {
            if (
                (uint256(uint8(bresult[i])) >= 48) &&
                (uint256(uint8(bresult[i])) <= 57)
            ) {
                if (decimals) {
                    if (_b == 0) break;
                    else _b--;
                }
                mint *= 10;
                mint += uint256(uint8(bresult[i])) - 48;
            } else if (uint256(uint8(bresult[i])) == 46) {
                require(
                    !decimals,
                    "More than one decimal encountered in string!"
                );
                decimals = true;
            } else {
                revert("Non-numeral character encountered in string!");
            }
        }
        if (_b > 0) {
            mint *= 10 ** _b;
        }
        return mint;
    }
}

 

 

 

参考

callData memory storage 的区别
solidity的一些知识点(下)
solidity: 转账实现
Solidity基础六
Solidity中的错误处理和异常
深入理解Solidity——事件(Events)
Solidity之事件
Solidity中的事件和日志
2.solidity 数值类型 bool、uint、int、address、bytes32
区块链智能合约solidity的中的一些关键字
区块链2——Solidity智能合约开发