一、智能合约

智能合约(英语:Smart Contract)是一种旨在以信息化方式传播、验证或执行合同的计算机协议。智能合约允许在没有第三方的情况下进行可信交易。这些交易可追踪且不可逆转。智能合约概念于1994年由Nick Szabo首次提出。

以上解释来自于维基百科

由于缺少可信的执行环境,智能合约并没有被应用到实际产业中,自比特币诞生后,人们认识到比特币的底层技术区块链天生可以为智能合约提供可信的执行环境,以太坊首先看到了区块链和智能合约的契合,发布了白皮书《以太坊:下一代智能合约和去中心化应用平台》,并一直致力于将以太坊打造成最佳智能合约平台,所以比特币引领区块链,以太坊复活智能合约。

在区块链上运行的程序,通常称之为"智能合约",所以通常我们将区块链程序称之为智能合约,在区块链上,由事件驱动,以代码形式存在,可执行的特殊交易合同,它是代码与数据的集合,是以太坊的核心。

1. Solidity语言

Solidity是以太坊智能合约的编程语言,语法接近于JavaScript,是一种面向对象的语言,用于智能合约的开发,并能够编程成以太坊虚拟机(EVM)字节码部署到以太坊底层区块链网络上,文件扩展名以.sol结尾,一般用于:

  • 投票
  • 众筹
  • 封闭拍卖
  • 多重签名钱包

等对信任,安全和持久性要求较高的应用场景。

2.EVM

类似于JVM,EVM是以太坊虚拟机(Ethereum Virtual Machine),是以太坊智能合约的运行环境,每个以太坊节点中都包含EVM,EVM是一个隔离的环境,在EVM内部运行的代码与外部没有关联,EVM运行在以太坊节点上,我们将智能合约部署到以太坊区块链网络后,合约就可以在以太坊网络环境中运行了。

3. 运行步骤

  • 编译

    以太坊虚拟机上运行的是字节码,需要我们在部署之前对合约进行编译转成字节码和ABI(二进制接口,是智能合约的接口说明)

  • 部署

    合约部署就是讲编译好的合约字节码,通过外部账号以发送交易的形势部署到以太坊的区块链网络上,有实际矿工出块之后,才算真正部署成功。

  • 运行

  • 合约部署后,当需要调用这个智能合约的方式时,只需要向这个合约账户发送消息(交易)即可,通过消息触发后智能黑夜的代码就会在EVM中执行。

二、开发环境

目前而言,还没有特别完善的Solidity开发工具,现在可选性并不多,用的最多的是Remix,大家暂且先用这个吧

以太坊智能合约入门项目-众筹项目

  • 文件夹

    最左边是文件夹管理,里面列出了当前的工作区里的文件,remix可以支持从本地文件夹读取文件。

  • 工作区

    正中间是工作区,工作区上半部是代码编辑区,在这里可以写solidity合约。
    下半部是日志区,在执行智能合约时,会显示transaction相关的信息。在输出日志的时候还可以查看Details和Debug信息。

  • 功能区
    最右边的是功能区,里面有编译,运行,设置和分析以及调试器和支持。

    在编译器点击Details可以查看编译细节,里面有NAME,METADATA,BYTECODE,ABI 等一些相关信息。

    在设置里面可以选择我们的编译器版本,和一些IDE的使用设置。

    功能区提供了三个运行环境

    • JavaScript VM: 模拟环境
    • Injected Web3: 可以通过Mist或者MetMask钱包提供
    • Web3 Provider: 通过IPC配置获取

在下面的众筹项目中,我们采用的是JavaScript VM,在该环境中,Remix由5个以太坊账户组成,每个账户存有100个以太币用于合约的测试

三、众筹项目

在该众筹项目中,包含了两个角色,关系为多对多。

  • Investor: 众筹赞助方
  • BySponsor:众筹发起方

为了对代码进行比较全面的解读,直接将注释写到代码中,不再单独取出代码块进行说明

// 声明solidity版本为0.4.24
pragma solidity ^0.4.24;
// 创建合约CrowFunding
contract CrowFunding {
//定义众筹赞助方结构体
    struct Investor{
        address addr; //赞助地址
        uint count ;  //赞助数量
    }
//定义众筹发起方结构体 
    struct BySponsor{
        address addr;       //接收地址
        uint goalCount;     //众筹金额
        uint receiveCount;  //已经众筹到的金额
        uint investorNum;   //众筹次数
        mapping (uint =>Investor) investors; //用于保存多个众筹赞助方信息
    }

    uint bySponsorNum = 0 ;  //定义众筹发起方的ID号
    mapping (uint=>BySponsor) bySponsors; //定义映射用于保存多个合作发起方信息

//定义函数,用于生成众筹发起方对象
    function newBySponsor() payable {
        // 将合约发起方ID号增加1
        bySponsorNum++;
        /* 创建合约发起方对象
        众筹地址为:当前Accout地址
        众筹金额为:通过Value值设置
        */
        BySponsor memory bySponsor = BySponsor(msg.sender,msg.value,0,0);
        //将该众筹对象保存至映射bySponsors中
        bySponsors[bySponsorNum] = bySponsor;

    }
 /*定义函数,用于查看创建的众筹金额    
 传入参数: bySponsorId   众筹发起方ID
 返回值:   goalCount    创建的众筹金额 
 */
    function getGoalCount(uint bySponsorId) constant returns (uint){
         // 通过bySponsorId从映射bySponsors中取出对应的bySponsor对象
        BySponsor memory bySponsor = bySponsors[bySponsorId];
        //返回bySponsor对象的goalCount属性值
        return bySponsor.goalCount;
    }

 //定义函数,用于实现众筹赞助方的赞助功能,传入参数为众筹发起方ID号,并定义函数类型为payable
    function sponsor(uint bySponsorId)payable {
        // 通过bySponsorId从映射bySponsors中取出对应的bySponsor对象
        BySponsor storage bySponsor = bySponsors[bySponsorId];
        //设置合约代码可执行条件是赞助的金额必须>0
        require(msg.value >0);
        // 将赞助金额加入到bySponsor对象的receiveCount属性中
        bySponsor.receiveCount += msg.value;
        //众筹次数累加
        bySponsor.investorNum++;
        //将本次众筹赞助方信息保存至映射investors中
        bySponsor.investors[bySponsor.investorNum] = Investor(msg.sender,msg.value);
        //实现转账,从当前地址(众筹赞助方地址)转入bySponsor(众筹接收方地址),金额为Value中定义的值
        bySponsor.addr.transfer(msg.value);
    }

 //定义函数,用于获取已经众筹到的金额,传入参数为众筹发起方ID号  
    function getReceiveCount(uint bySponsorId) constant returns (uint){
         // 通过bySponsorId从映射bySponsors中取出对应的bySponsor对象
        BySponsor memory bySponsor = bySponsors[bySponsorId];
        //返回bySponsor对象的receiveCount属性值
        return bySponsor.receiveCount;
    }    
//定义函数,用于检查是否众筹金额是否达标,传入参数为众筹发起方ID号 
    function checkComplete(uint bySponsorId) constant returns (bool){
         // 通过bySponsorId从映射bySponsors中取出对应的bySponsor对象
        BySponsor bySponsor = bySponsors[bySponsorId];
        // 判断设定的众筹金额是否有效(不为0),并且已经众筹到的金额是否大于等于创建的众筹金额
        if (bySponsor.receiveCount >= bySponsor.goalCount && bySponsor.goalCount >0){
            return true;
        }else {
            return false;
        }
    }
}

四、项目测试

智能合约运行步骤已在上文提到

1. 编译合约

以太坊智能合约入门项目-众筹项目

  • 此处选择了自动编译,无需手动编译
  • 选择合约为CrowFunding

2. 部署合约

以太坊智能合约入门项目-众筹项目

  • 点击Run标签
  • 选择JavaScript VM环境,账户随意选择一个即可
  • 选择对应的合约CrowFunding
  • 选择Deploy按钮进行合约部署

3. 运行合约

  • 运行newBySponsor函数创建众筹信息

以太坊智能合约入门项目-众筹项目

调用方法

  • 查看被赞助前相关信息

以太坊智能合约入门项目-众筹项目

调用方法查看众筹设定金额,已经众筹到的金额,是否众筹完成等信息,注意此时ID为1

  • 执行捐助函数

以太坊智能合约入门项目-众筹项目

切换Account =>定义捐助金额 =>填写捐助对象(ID为1) =>执行sponsor

以太坊智能合约入门项目-众筹项目

日志信息

  • 查看捐助后相关信息

以太坊智能合约入门项目-众筹项目

再次执行捐助100后,查看结果

以太坊智能合约入门项目-众筹项目

至此,该众筹案例代码测试完毕

文中代码已上传至github中

https://github.com/DiaboFong/constractDemos/blob/master/demoCrowFunding.sol

以太坊智能合约入门项目-众筹项目