【区块链】以太坊Solidity编程:智能合约实现之函数与合约

以太坊Solidity编程:智能合约实现之函数与合约

函数及基本控制结构

函数类型

  • 函数也是一个类型,且属于值类型
  • 可以将一个函数赋值给一个变量赋值给一个变量,一个函数类型的变量
  • 还可以将一个函数作为参数进行传递
  • 也可以在函数调用中返回一个函数
  • 函数类型有两类,可分为internal和external
  1. 内部函数(internal):不能在当前合约的上下文环境以外的地方执行,内部函数只能在当前合约内被使用。如在当前的代码块内,包括库函数,和继承的函数中。
  2. 外部函数(External):外部函数由地址和函数方法签名两部分组成。可作为外部函数调用的参数,或者由外部函数调用返回。

函数类型的定义

function (param types) {
     internal|external} [pure|constant|view|payable][returns (return types)] varName;
  • 如果不写类型,默认的函数类型是internal的
  • 如果函数没有返回结果,则必须省略returns关键字【区块链】以太坊Solidity编程:智能合约实现之函数与合约_第1张图片

函数的定义

function f(<parameter types>) {
     internal|external} [pure|constant|view|payable][returns (<return types>)]{
      // function body}

示例:

contract SimpleFunc {
     
	function hello(uint i) {
     
		// todo
	}
}

入值和返回值

  • 同JavaScript一样,函数有输入参数,但与之不同的是,函数可能有任意数量的返回参数
  • 入参(Input Parameter)与变量的定义方式一致,稍微不同的是,不会用到的参数可以省略变量名称
  • 出参(Output Parameters)在returns关键字后定义,语法类似变量的定义方式
    示例:【区块链】以太坊Solidity编程:智能合约实现之函数与合约_第2张图片
  • Solidity内置支持元素(tuple),这种内置结构可以同时返回多个结果
    示例
    【区块链】以太坊Solidity编程:智能合约实现之函数与合约_第3张图片

函数控制结构

  • 支持if、else、while、do、for、break、continue、return、?:
  • 条件判断中的括号不可省略,但在单行语句中的大括号可以省略
  • 无Boolean类型的自动转换,比如if(1){…}在Solidity中是无效的
  • 没有switch

函数调用

  • 内部调用:同一个合约中,函数互相调用。调用在EVM中被翻译成简单的跳转指令
  • 外部调用:一个合约调用另外一个合约的函数,或者通过web3调用合约函数。调用通过消息调用完成(bytes24类型,20字节合约地址+4字节的函数方法签名)。
    示例:
    【区块链】以太坊Solidity编程:智能合约实现之函数与合约_第4张图片

命名参数调用和匿名函数参数

  • 函数调用的参数,可以通过指定名字的方式调用,但可以以任意的顺序,使用方式为{}包含。参数的类型和数量要与定义一致。
    示例:
    【区块链】以太坊Solidity编程:智能合约实现之函数与合约_第5张图片

省略函数参数名称

  • 没有使用的参数名可以省略
    示例:
    【区块链】以太坊Solidity编程:智能合约实现之函数与合约_第6张图片

变量作用范围

  • 一个变量在声明后都有初始值为字节表示的全0
  • Solidity使用了JavaScript的变量作用范围的规则
  • 函数内定义的变量,在整个函数中均可用,无论它在哪里定义
    示例:
    【区块链】以太坊Solidity编程:智能合约实现之函数与合约_第7张图片

函数可见性

函数的可见性

  • 函数类型:internal和external
  • 访问方式:内部访问和外部访问
  • 处于访问控制的需要,函数具有“可见性(Visibility)类型”。
  • 状态变量也具有可见性类型

可见性类型

  • external:“外部函数”,可以从其他合约或者通过交易来发起调用。合约内不能直接调用。
  • public:“公开函数”,可以从合约内调用,或者消息来进行调用
  • Internal:“内部函数”,只能在当前合约或继承的合约里调用
  • private:“私有函数”,仅在当前合约中可以访问,在继承的合约内,不可访问。
    可见性图
    【区块链】以太坊Solidity编程:智能合约实现之函数与合约_第8张图片

默认访问权限

  • 函数默认是public
  • 状态变量默认的可见性是internal
    【区块链】以太坊Solidity编程:智能合约实现之函数与合约_第9张图片
    合约声明示例
    【区块链】以太坊Solidity编程:智能合约实现之函数与合约_第10张图片
    【区块链】以太坊Solidity编程:智能合约实现之函数与合约_第11张图片
    【区块链】以太坊Solidity编程:智能合约实现之函数与合约_第12张图片

状态变量访问

  • 编译器为自动为所有的public的状态变量创建访问函数。
  • 生成的函数名为参数名称,输入参数根据变量类型来顶。如uint无须参数,uint[]则需要输入参数数组下标作为参数
    示例:
    【区块链】以太坊Solidity编程:智能合约实现之函数与合约_第13张图片
    【区块链】以太坊Solidity编程:智能合约实现之函数与合约_第14张图片

函数修饰符

  • 修改器(Modifiers)可以用来改变一个函数的行为
  • 一般用于在函数执行前检查某种前置条件。
  • 修改器是一种合约属性,可被继承,同时还可派生的合约重写。
  • 函数可以有多个修改器,它们之间以空格隔开,修饰器会依次检查执行。
    示例:
    【区块链】以太坊Solidity编程:智能合约实现之函数与合约_第15张图片

函数属性

  • 根据对状态变量的修改情况,函数可以拥有pure|constant|view|public四个属性。
  • 属性定义放置在函数可见性之后
  • 不强制使用
  • 主要是为了节省gas

属性介绍

  • 只有当函数有返回值的情况下,才需要使用pure、view、constant
  • prue:可以读取状态变量但是不能改。
  • view:不能改也不能读状态变量,否则编译通不过
  • constant:view的旧版本,v4.17之后不建议
  • 带关键字pure或view,就不能修改状态变量的值。默认只是向区块链读取数据,读取数据不需要花费gas。
    状态变量的属性:
  • 状态变量可以声明为constant,同一般语言的常量,目前只支持值类型和String。
  • public的状态变量,其getter函数属性为view。

事件

  • 事件是使用EVM日志内置功能的方便工具
  • 事件在合约中可被继承
  • 当被调用时,会触发参数存储到交易的日志中
  • 可以最多有3个参数被设置为indexed,来设置是否被索引。
  • 设置为索引后,可以允许通过这个参数来查找日志,甚至可以按特定的值过滤。
    【区块链】以太坊Solidity编程:智能合约实现之函数与合约_第16张图片

错误和异常处理

Solidity的异常处理

  • Solidity使用“状态恢复”来处理错误
  • 有某些情况下,异常是自动抛出的
  • 抛出异常的效果是当前的执行被终止且被撤销(值的改变和账户余额的变化都会被回退)
  • Solidity暂时没有捕捉异常的方法(try…catch)

assert/require:

  • 函数assert和require来进行条件检查,如果条件不满足则抛出异常
  • assert函数通常用来测试内部错误
  • require函数来检查输入变量或合约状态变量是否满足条件,以及验证调用外部合约返回值
    【区块链】以太坊Solidity编程:智能合约实现之函数与合约_第17张图片
    revert/throw
  • revert函数可用于标记错误
  • throw同revert类似,已不建议使用

三个异常示例(等价的):
【区块链】以太坊Solidity编程:智能合约实现之函数与合约_第18张图片
常见异常

  • 数组访问越界,或是负的序号值访问数组
  • 调用require/assert,但参数值为false
  • .transfer()执行失败
  • 如果你的public的函数在没有payable关键字时,却尝试在接收ether
  • 如果你通过消息调用一个函数,但在调用的过程中,并没有正确结果(如gas不足等)

合约与继承

合约

  • Solidity中合约类似面向对象语言中的类。
  • 合约可以继承。
  • 一个合约可以调用另外一个合约。
  • 一个合约中可以创建另外一个合约。
  • 合约操作另外一个合约,一般都需要直到其代码。

合约间调用

contract OwnedToken{
     
	// TokenCreator是一个合约类型
	// 未初始化前,为一个引用
	TokenCreator creator;
	address owner; // 状态变量
	bytes32 name; // 状态变量

	// 构造函数
	function OwnedToken(bytes32 _name) public{
     
		owner = msg.sender;
		creator = TokenCreator(msg.sender);// 另外一个合约
		name = _name;
	}
}

合约中创建合约

  • 一个合约可以通过new关键字来创建一个合约。
  • 要创建合约的完整代码,必须提前知道
contact TokenCreator{
     
	function createToken(bytes32 name) public returns (OwnedToken){
     
		// 创建一个新的合约,name为构造函数所需变量
		OwnedToken tokenAddress = new OwnedToken(name);
		return tokenAddress;
	}
}

继承

  • Solidity通过复制包括多态的代码来支持多重继承。基本的继承体系与python类似
  • 当一个合约从多个其他合约那里继承,在区块链上仅会创建一个合约,在父合约里的代码会复制来形成继承合约。
  • 派生的合约需要提供所有父合约需要的所有参数。
contract Owned {
     
	function owned() {
      owner = msg.sender; }
	address owner;
}

// 使用`is`来继承另外一个合约
// 子合约可以使用所有的非私有变量,包括内部函数和状态变量
contract Mortal is Owned {
     
	function kill() {
     
		if (msg.sender == owner) selfdestruct(owner);
	}
}

几种特殊的合约

  • 抽象合约(Abstract Contracts)
  1. 合约包含抽象函数,也就是没有函数体的函数
  2. 这样的合约不能通过编译,即使合约内也包含一些正常的函数
  3. 抽象合约一般可以做为基合约被继承
contact Feline{
     
	// 函数没有函数实现,为抽象函数,对应的合约为抽象合约
	function utterance() returns (bytes32);
	 
	function getContractName() returns (string) {
     
		return "Feline";
	}
}

contract Cat is Feline{
     
	// 继承抽象合约,实现函数功能
	function utterance() returns (bytes32) {
     return "miaow";}
}
  • 接口(Interfaces)
  1. 接口与抽象合约类似,与之不同的是,接口内没有任何函数是已实现的,同时还有如下限制:
  2. 不能继承其他合约,或接口
  3. 不能定义构造器
  4. 不能定义变量
  5. 不能定义结构体
  6. 不能定义枚举类
  7. 接口基本上限制为合约ABI定义可以表示的内容
// 接口
interface Token {
     
	function transfer(address recipient, uint amount);
}

// 接口可以被
contract MyToken is Token {
     
	function transfer(address recipient, uint amount){
     
		// 函数发现
	}
}
  • 库(Libraries)
  1. 库与合约类似,但它的目的是在一个指定的地址,且仅部署一次,然后通过EVM的特性来复用代码。
  2. 使用库合约的合约,可以将库合约视为隐式的父合约(base contracts),不会显示的出现在继承关系中。
  3. 调用库函数的方式非常类似,如库L有函数f(),使用L.f()即可访问
library Set{
     
	struct Data {
      mapping(uint => bool) flags; }
	
	// 第一个参数的类型为“storage reference”, 仅存储地址,而不是这个库的特别特征
	// 按照一般语言的惯例,`self`代表第一个参数
	function insert(Data storage self, uint value)
		public
		return (bool)
	{
     
		if (self.flags[value])
			return false; //already there
		self.flags[value] = true;
		return true;
	}
}
contract C{
     
	Set.Data knownValues;
	
	function register(uint value) public{
     
		// 库可以直接调用,而无需使用this
		requires(Set.insert(knownValues, value);
	}	
}

实战:奖品竞拍

项目需求

  • 大家之前都拿到了不少课程积分,为活跃课程氛围,特开展奖品竞拍活动:
    1. 大家用课程积分竞拍,采用连续竞价、明拍、限时模式。
  • 奖品为电商兑换码,可以兑换不同学习物品
  • 一次拍卖完成后,积分概不退还,可开始下一次拍卖。

基本数据结构

  • 积分体系:完全使用之前的Mycoin即可,使用继承
  • 拍卖列表:用户和出价,使用一个mapping即可
  • 出价最高用户/出价:全局状态变量
  • 限时/兑换码/竞拍状态:全局状态变量

功能需求:竞价

  1. 连续竞价,和之前的出价叠加
  2. 必须高于当前最高出价才算出价成功。
  3. 出价的积分打入合约地址

功能需求:竞价完成

  1. 结束时间已到
  2. 之前没有领取过
  3. 最高出价用户领取
  4. 全局状态变量管理员可以强制终止拍卖

功能需求:重开竞价

  1. 只有管理员可以重开
  2. 重新设置兑换码
  3. 重新设置兑换时间
  4. 重置竞拍状态

代码

MyCoin.sol

pragma solidity >=0.6.4;


contract MyCoin{
     
    //数据结构
    //1.用户
    address[] userList;
    mapping(address=>uint8) userDict;

    //2.用户的币
    mapping(address=>uint) balances;

    //3.用户交易,第一个address为交易发起方,第二个为接收方,uint[]为交易量记录
    mapping(address => mapping(address=>uint[])) public trans;

    //用户初次可领取的数量
    uint iniCount = 100;

    function Mycoin() public{
     
        //合约本身拥有的币
        balances[address(this)] = 100000;
    }

    //领取货币,只能一次
    function getCoin() public returns (bool sufficientAndFirstGet) {
     
        //判断合约是否钱足够
        if(balances[address(this)]<iniCount) return false;

        //判断是否从合约领取过币
        if(0!=trans[address(this)][msg.sender].length) return false;

        //领取币
        balances[address(this)] -= iniCount;
        balances[msg.sender] += iniCount;

        //记录交易
        trans[address(this)][msg.sender].push(iniCount);

        //加入用户列表
        if(0 == userDict[msg.sender]){
     
            userList.push(msg.sender);
            userDict[msg.sender] = 1;
        }
        return true;
    }

    //发送货币
    function sendCoin(address receiver, uint amount) public returns(bool sufficient){
     
        //判断是否还有足够的币
        if(balances[address(this)]<amount) return false;

        //发生交易
        balances[msg.sender] -= amount;
        balances[receiver] += amount;

        //记录交易
        trans[msg.sender][receiver].push(amount);

        //加入用户列表
        if (0 == userDict[receiver]) {
     
            userList.push(receiver);
            userDict[receiver] = 1;
        }

        return true;
    }

    //获得某个用户的货币量
    function getBalances(address addr) public returns(uint){
     
        return balances[addr];
    }

    //获得领取的进度
    function getPercent() public returns(uint){
     
        uint sum = 0;
        for(uint i = 0;i < userList.length; i++){
     
            address userAddress = userList[i]; //用户的地址
            sum = sum + balances[userAddress];
        }
        return (100*sum)/100000;
    }

    //获得币最多的用户地址
    function getBest() public returns (address){
     
        address maxAdd;
        uint maxCoin = 0;

        //获得币最多的用户地址
        for(uint i = 0; i < userList.length; i++){
     
            address userAddress = userList[i]; //用户地址
            uint userCoin = balances[userAddress]; //用户积分
            if(userCoin > maxCoin){
     
                maxAdd = userAddress;
                maxCoin = userCoin;
            }
        }
        return maxAdd;
    }
}

MyBid.sol

pragma solidity >=0.6.4;
import "./MyCoin.sol";


contract MyBid is MyCoin{
     
    //数据结构
    string private ticket;//电商兑换码
    address owner;//合约创建者

    //时间是unix的绝对时间戳
    uint public auctionEnd;

    // 拍卖的当前状态
    address public highestBidder; //最高出价用户的地址
    uint public highestBid; //最高出价

    //出价列表
    mapping (address => uint) bids;

    // 拍卖结束后设为 true,将禁止所有的变更
    bool ended;

    //事件
    event HighestBidIncreased(address bidder, uint amount);
    event AuctionEnded(address winner, uint amount);

    //dev 创建一个简单的拍卖
    //_biddingTime,拍卖时间;_ticket,兑换码
    constructor (uint _biddingTime, string memory _ticket) public{
     
        ticket = _ticket;
        auctionEnd = block.timestamp + _biddingTime;
        owner = msg.sender;
    }

    // 竞价,value为加价,和之前的出价叠加
    function bid(uint value) public {
     
        // 如果拍卖已结束,撤销函数的调用。
		require(block.timestamp < auctionEnd);
        // 如果出价不够高,不继续执行
		uint actualValue = bids[msg.sender] + value;
		require(actualValue > highestBid);

        //把积分发送到合约地址,已经检查额度
		sendCoin(address(this),value);
		
        //更新出价和最高出价
		bids[msg.sender] = actualValue;
		highestBidder = msg.sender;
		highestBid = actualValue;
		
		emit HighestBidIncreased(msg.sender, actualValue);
    }

    // 结束拍卖,并把最高的出价发送给受益人
    function auctionEnded() public payable returns (string memory){
     
        // 1. 条件
        require(now >= auctionEnd,"Not End!"); // 拍卖尚未结束
        require(!ended); // 该函数已被调用

        //必须是最高出价用户调用,或者管理员强制终止
		require(msg.sender == highestBidder || msg.sender == owner);
        // 2. 生效
		ended = true;
		emit AuctionEnded(highestBidder, highestBid);
        // 3. 返回ticket,只能查看一次
        return ticket;
    }

    // `_;` 表示修饰符,可代表被修饰函数位置
    modifier onlyOwner {
     
        require(msg.sender == owner);
        _;
    }

    //开始新的拍卖
    function newBid(uint _biddingTime, string memory _ticket) public onlyOwner {
     
        // 1. 条件
		require(now >= auctionEnd);
		require(ended);

        //重置数据
		ticket = _ticket;
		auctionEnd = now + _biddingTime;
		ended = false;
		delete highestBidder; //重置最高出价用户的地址
		delete highestBid; //重置最高出价
		//delete bids[msg.sender];
    }
}

注意:可能由于版本问题存在一些错误,如有参考,请及时更正!

你可能感兴趣的:(区块链与金融科技,区块链,Solidity,函数,合约,以太坊)