区块链DAPP

【DAPP开发三】发布合约及实践

2018-09-25  本文已影响48人  GeniusWong

4.1.1 语法介绍 block/msg/now

    block.blockhash(uint blockNumber) returns (bytes32) 给定块的哈希 - 仅适用于256个不包括当前最新块
    block.coinbase (address) 当前块矿工地址
    block.difficulty (uint) 当前块难度
    block.gaslimit (uint) 当前块gaslimit
    block.number (uint) 当前数据块号
    block.timestamp (uint) 当前块时间戳从unix纪元开始为秒
    msg.data (bytes) 完整的 calldata
    msg.gas (uint) 剩余gas
    msg.sender (address) 该消息(当前呼叫)的发送者
    msg.sig (bytes4) 呼叫数据的前四个字节(即功能标识符)
    msg.value (uint) 发送的消息的数量

4.1.2 存储 storage / memory

Storage 变量是指永久存储在区块链中的变量。 Memory 变量则是临时的,当外部函数对某合约调用完成时,
内存型变量即被移除。 你可以把它想象成存储在你电脑的硬盘或是RAM中数据的关系。大多数时候你都用不到这
些关键字,默认情况下 Solidity 会自动处理它。 状态变量(在函数之外声明的变量)默认为“存储”形式,
并永久写入区块链;而在函数内部声明的变量是“内存”型的,它们函数调用结束后消失。  

4.1.3 随机数

生成一个0-100 之间的随机数

function randomUtils() public view returns(uint) {
      //定义数字。
      uint random=1;
      //基于数字的基础上生成随机数.
      //keccak256 生成数.
      return uint(keccak256(now,msg.sender,random)) % 100;
}

4.1.4 require 关键字

     require(keccak256(_name) == keccak256("Vitalik"));
     require使得函数在执行过程中,当不满足某些条件时抛出错误,并停止执行:
     用来比较两个字符串是否相等.
     function equals(string str, string str1) public pure returns (bool) {
          return (keccak256(str) == keccak256(str1));
    }

4.1.5 modify


pragma solidity^0.4.19;
contract ModifyContract {
     address owner;  
     //定义占位符,可以把需要判断的条件共性的地方进行抽取。          
     modifier onlyOwner() {
            require(owner == msg.sender);
            _;      
     }
     //在调用方法之前会先进行检查.
     function getRightToVote() public  onlyOwner {

     }
}

4.2发布合约

使用 Remix IDE,写入代码后,选择 solc 版本,再进行编译。然后到 Run Tab页,选择好 环境,账户 等信息,设置 Gas Limit (一般 Remix 会自动设置一个值),点击 Create 发布合约。发布合约需要花费 Gas。
合约发布之后,用户可以点击合约函数,执行函数。有一些函数需要 Gas,而有一些函数则不需要 Gas。

4.5调试

进入 Debugger Tab 页,可以进行调试。

5 solidity 急行军

案例1 转账给智能合约账户

当一个智能合约运行时,它运行在以太坊上,任何人都可以调用函数,向智能合约转钱

pragma solidity^0.4.24;
contract Money {
     function Money(){
     }
     //向智能合约账户转钱
     function paymoney () payable public{    
     }  
     function getBalance() public view returns (uint) {
            return address(this).balance;
     }   
}

案例2 从智能合约账户取钱

这个例子,展示如何从智能合约账户取钱,在这个例子里,取钱没有任何条件,
只要合约账户中有钱,就可以取出这部分钱来

pragma solidity^0.4.19;

contract GetMoney {
      //合约发布者
      address owner;
      //发布合约的时候会调用构造函数.
      function GetMoney() public {
           owner = msg.sender;
      }
      //向合约账户转钱。
      function payMoney() payable public {

      }
      //查看智能合约账户的余额  
      function getBalance() public view returns(uint) {
             return address(this).balance;
      }
      //谁调用就往谁的账户打钱。从智能转化里面转钱.
      function getMoney() public {
            address who = msg.sender;
            if(getBalance() > 2 ether) {
                 who.transfer(2 ether);
            }
      }
      //销毁合约.
      function kill() public {
           //判断操作,如果合约的发布者是调用着,则有权限销毁合约.
           if(owner ==  msg.sender) {
                 selfdestruct(msg.sender);
           }
      }              
}

案例3 土豪发红包

```JavaScript
pragma solidity^0.4.19;

contract RedPacket {
     //设置土豪.
     address tuhao;
     //初始化红包的个数.
     int number;
     //初始化相关数据.
     function RedPacket(int _number) public payable{
          tuhao = msg.sender;
          number = _number;
     }
     //获取合约的余额.
     function getBalance() public view returns(uint){
           return address(this).balance;  
     }
     //抢红包
     function stakeMoney() public payable returns(bool) {
           address who = msg.sender;
           if(number > 0) {
               number --;
                uint random = uint(keccak256(now,msg.sender,10)) % 100;
               uint balance = getBalance();
               who.transfer(balance * random / 100);
               return true;
           }
           return false;
     }
     //destory contract
     function kill() public {
          if(tuhao == msg.sender) {
                selfdestruct(tuhao);
          }
     }
}
```

案例4 博彩赌大小.

pragma solidity^0.4.19;

contract Bet {

     address owner; //contract manager

     struct Player {
          address addr;
          uint money;
     }

     Player [] inbig;
     Player [] insmall;

     uint blockNumber;
     uint totalBig;
     uint totalSmall;

     function Bet() public {
          owner = msg.sender;
          blockNumber = block.number;
          totalBig = 0;
          totalSmall = 0;
     }

     function getBalance() public view returns(uint) {
          return address(this).balance;
     }

     function getBlockNumber() public view returns(uint,uint){
           return (blockNumber,block.number);
     }

     //押注 大小.
     function stake(bool flag) public view returns (bool){
           //先结构化玩家.
           Player memory player  = Player(msg.sender,msg.value);
           //玩家是否带钱过来.  
           if(player.money > 0){
                return false;
           }
           //押注大的
           if(flag){
                 inbig.push(player);
                 totalBig +=player.money;
           }else{ //押注小的.
                 insmall.push(player);
                 totalSmall +=player.money;
           }
           return true;
     }

     //开奖。
     function open()  payable public  returns (bool) {
            //设置开奖限制,必须最少得有两个人押注.
            if(block.number < 2+blockNumber) {
                return false;
            }
            //押注大大金额以及押注小大金额大比例。
            if(totalSmall == 0 && totalBig == 0){
                return false;
            }
            //计算开大开小的规则,根据当前块的hash 值去定.
            uint hash =  uint(block.blockhash(block.number));
            uint points = hash % 18;     
            uint i=0;    
            uint count;  
            Player memory player;
            if(points>9) {  // big winer
                  count=inbig.length;
                  for(i=0;i<count;i++){
                         player = inbig[I];
                         player.addr.transfer(player.money+totalSmall*player.money/totalBig);
                  }
            }else { // small winer
                  count = insmall.length;
                  for(i=0;i<count;i++){
                       player = insmall[I];
                       player.addr.transfer(player.money+totalBig*player.money/totalSmall);
                  }
            }
            return true;
     }
     //销毁合约
     function kill() public {
          if(msg.sender == owner){
              selfdestruct(owner);
          }
     }
}

案例5 社区投票

需求:
投票,委托代理人投票,给某个voter 赋予投票的权利
获取到最高的选题Prososal的name

//合约名
contract Ballot {
    // 投票者结构体
    struct Voter {
        uint weight; // 份额(既拥有多少票)
        bool voted;  // 是否已经投票
        address delegate; // 委任谁进行投票
        uint vote;   // 第几个投票议案
    }

    // 议案机构体
    struct Proposal {
        bytes32 name;   // 议案名(最多32字节)
        uint voteCount; // 累计投票数
    }

    address public chairperson; //投票主持人

    // 投票者与其地址的映射
    mapping(address => Voter) public voters;

    // 提案指针
    Proposal[] public proposals;

    /// Ballot函数,创建新投票,输入多个议案名
    function Ballot(bytes32[] proposalNames) public {
        chairperson = msg.sender;
        voters[chairperson].weight = 1;

        for (uint i = 0; i < proposalNames.length; i++) {
            // `Proposal({...})` 创建一个临时Proposal对象
            proposals.push(Proposal({
                name: proposalNames[I],
                voteCount: 0
            }));
        }
    }

    //输入投票者地址,给予投票者投票权限
    function giveRightToVote(address voter) public {
        // require防止函数被错误调用,判断为错误时终止调用
        // 并恢复到调用前的状态,但是注意会消耗gas
        require(
            (msg.sender == chairperson) &&
            !voters[voter].voted &&
            (voters[voter].weight == 0)
        );
        voters[voter].weight = 1;
    }

    /// 输入他人地址,委任他人投票
    function delegate(address to) public {
        Voter storage sender = voters[msg.sender];
        require(!sender.voted);

        // 不允许委任自己
        require(to != msg.sender);

        // 循环委任直至被委任人不再委任他人,
        // 但注意这种循环是危险的,有可能耗尽gas来进行计算。
        while (voters[to].delegate != address(0)) {
            to = voters[to].delegate;

            // 避免循环委任,形成委任环链
            require(to != msg.sender);
        }

        // 此处sender为voters[msg.sender]
        sender.voted = true;
        sender.delegate = to;
        Voter storage delegate_ = voters[to];
        if (delegate_.voted) {
            // 如果被委任人已经投票,直接增加该议案票数
            proposals[delegate_.vote].voteCount += sender.weight;
        } else {
            // 如果被委任人未投票,增加被委任人持有票数
            delegate_.weight += sender.weight;
        }
    }
    /// 向议案投票
    function vote(uint proposal) public {
        Voter storage sender = voters[msg.sender];
        require(!sender.voted);
        sender.voted = true;
        sender.vote = proposal;

        // 提案超出数组范围时自动中断并恢复所以修改
        proposals[proposal].voteCount += sender.weight;
    }
    /// 返回获得票数最高的议案索引
    function winningProposal() public view
            returns (uint winningProposal_)
    {
        uint winningVoteCount = 0;
        for (uint p = 0; p < proposals.length; p++) {
            if (proposals[p].voteCount > winningVoteCount) {
                winningVoteCount = proposals[p].voteCount;
                winningProposal_ = p;
            }
        }
    }
    // 返回获得票数最高的议案名
    function winnerName() public view
            returns (bytes32 winnerName_)
    {
        winnerName_ = proposals[winningProposal()].name;
    }
}

案例6 竞拍 (代码)

contract SimpleAuction {
    
    address public beneficiary;
    //竞拍开始
    uint public auctionStart;
    uint public biddingTime;

    //当前的拍卖状态
    address public highestBidder;
    uint public highestBid;

   //在结束时设置为true来拒绝任何改变
    bool ended;

   //当改变时将会触发的Event
    event HighestBidIncreased(address bidder, uint amount);
    event AuctionEnded(address winner, uint amount);

    //下面是一个叫做natspec的特殊注释,
    //由3个连续的斜杠标记,当询问用户确认交易事务时将显示。

    ///创建一个简单的合约使用`_biddingTime`表示的竞拍时间,
   /// 地址`_beneficiary`.代表实际的拍卖者
    function SimpleAuction(uint _biddingTime,
                           address _beneficiary) {
        beneficiary = _beneficiary;
        auctionStart = now;
        biddingTime = _biddingTime;
    }

    ///对拍卖的竞拍保证金会随着交易事务一起发送,
   ///只有在竞拍失败的时候才会退回
    function bid() {

       //不需要任何参数,所有的信息已经是交易事务的一部分
        if (now > auctionStart + biddingTime)
           //当竞拍结束时撤销此调用
            throw;
        if (msg.value <= highestBid)
           //如果出价不是最高的,发回竞拍保证金。
            throw;
        if (highestBidder != 0)
            highestBidder.send(highestBid);
        highestBidder = msg.sender;
        highestBid = msg.value;
        HighestBidIncreased(msg.sender, msg.value);
    }

   ///拍卖结束后发送最高的竞价到拍卖人
    function auctionEnd() {
        if (now >= auctionStart + biddingTime)
            throw; 
            //拍卖还没有结束
        if (ended)
            throw; 
     //这个收款函数已经被调用了
        AuctionEnded(highestBidder, highestBid);
        //发送合约拥有所有的钱,因为有一些保证金可能退回失败了。

        beneficiary.send(this.balance);
        ended = true;
    }

    function () {
        //这个函数将会在发送到合约的交易事务包含无效数据
        //或无数据的时执行,这里撤销所有的发送,
        //所以没有人会在使用合约时因为意外而丢钱。
        throw;
    }
}

博彩游戏

需求部分:

玩法规则

【缺图】

合约需求

合约代码实例

pragma solidity ^0.4.19;
contract LotteryBase {
    address public manager; // 彩票管理员
    string public name; // 彩票名称
    uint public baseBlock; // 当前售卖到的彩票期数 基准区块,也是彩票的期数 10
    uint public stopBlock; // 彩票从当前块数+到特定块数这一期间为售卖期间 关闭彩票售卖通道区块 100
    uint public openBlock; // 到了特定期间后再+到特定期间为开奖期间 开奖区块 110
    //开奖期数号码记录,用于彩民备查
    struct OpenCode {
        uint block; // 等于 baseBlock
        uint8[] opencode; // 开奖号码
    }
    // 所有开奖号码集合
    OpenCode[] public allOpenCodes;
    // 奖金账本,记录每一个用户的奖金
    mapping(address=>uint) public bonus; // 奖金
    // 所有中奖用户的奖金综合
    uint public totalBonus; // 总奖金金额,奖池金额 = 余额 - 奖金总金额

    function LotteryBase () public {
        manager = msg.sender;
    }

    modifier onlyManager () {
        require (manager == msg.sender);
        _;
    }

    // 两个抽象接口
  //  function chipin(uint8[] number, uint8 multi) public payable;
    //function open() public;

    // 未分配奖池的奖金金额
    function getBonusPool() public view returns (uint) {
        return address(this).balance - totalBonus;
    }
}

contract KuaiSan is LotteryBase {

     //不同下注不同赔率列表
    uint8[19] public odds=[0,0,0,240,80,40,25,16,12,10,9,9,10,12,16,25,40,80,240];
    //定义下注方式的枚举类型
    enum InjectionType {TypeSum, TypeSameThree,TypeSameThreeSingle,TypeSameTwo,TypeSameTwoSingle,TypeNoSameThree,TypeNoSameTwo,TypeThreeConsecutive }
    //购买彩票的结构体
    struct Order {
        address player;
        uint8[] number; // 彩票号码
        uint8 multi;
        InjectionType injectionType;//投注类型
        uint8 sumVal;//如果是和值类型,那么该值为下注的和值
    }
    // 订单集合
    Order[] orders;

    // 彩票价格,大约2人民币
    uint constant fee = 0.001 ether;
    uint constant stopInterval = 30; //设置停止间隔
    uint constant openInterval = 40; //设置开奖间隔
    uint constant codeAOffset = 33; //这只当前区块+33的hash值给A数
    uint constant codeBOffset = 34;//这只当前区块+33的hash值给B数
    uint constant codeCOffset = 35;//这只当前区块+33的hash值给C数

    function KuaiSan() public {  //构造函数,开始竞猜
        // 开奖将基于 baseBlock + 33, baseBlock + 34, baseBlock + 35
        baseBlock = block.number; //将当前区块设置到baseBlock中
        stopBlock = baseBlock + stopInterval; //设置停止区块为当前区块+开奖间隔 about 450 seconds
        openBlock = baseBlock + openInterval; //设置开奖区块为当前区块+开奖区块 about 150 seconds

        name = "中国福利彩票北京快三";
    }

    // 下注,玩家提供一个号码和倍数,记录到订单中
    // number是玩家购买的号码,multi是玩家要购买的倍数,injType为玩家下注方式,_sumVal是和值类型的和值数
    function chipin(uint8[] number, uint8 multi, InjectionType injType, uint8 _sumVal) public payable {
        require(msg.value >= fee * multi); //检查下玩家投注的金额和倍数是不是一样
        require(block.number <= stopBlock); //检查当前块数是可以下注的块数

        if(injType == InjectionType.TypeSum ){ //判断玩家下注方式是不是和值类型
            require(_sumVal >= 3 && _sumVal <= 18); //检查和值数不能小于3并且不能大于18
        }
        Order memory order = Order(msg.sender, number, multi, injType, _sumVal); //新建一个Order结构体
        orders.push(order);//将Order结构体存储起来
    }
    //计算奖金函数-通过下注code信息计算奖金情况,返回奖金值
    //number是玩家购买的号码,sumVal是值数,multi是玩家要购买的倍数,injType为玩家下注方式,openCode是开奖号码
    function calcBonus(uint8[] number,uint8 sumval, uint8 multi, InjectionType injectionType,uint8[] opencode) public view returns (uint){

        //对于和值类型投注,下注金额不能小于1大于18

        //如果是和值类型,计算和值是否相等
        if(injectionType == InjectionType.TypeSum){ //如果玩家选择的是和值类型
            uint injVal = opencode[0]+opencode[1]+opencode[2]; //开奖号码总和
            if(sumval == injVal){ //如何玩家的sumval和开奖号码和值相等的话
                return fee * odds[injVal] * multi / 2; //返回该获得的奖金
            }
            return 0;
        }
        //三同号通选 要求三个号码相同即可
        if(injectionType == InjectionType.TypeSameThree){
            if(opencode[0]  == opencode[2]){
               return fee * 40 * multi / 2;
            }
            return 0;
        }
        //三同号单选
        if(injectionType == InjectionType.TypeSameThreeSingle){
            if(opencode[0] == opencode[2]  && number[0] == opencode[0]){
                return fee * 240 * multi / 2;
            }
            return 0;
        }
        //二同号复选 只要任意两个号码相同即可
        if(injectionType == InjectionType.TypeSameTwo){
           if((opencode[0] == opencode[1] || opencode[1] == opencode[2]) && opencode[1] == number[0] ){
                return fee * 15 * multi / 2;
            }
            return 0;
        }
        //二同号单选 要求指定的对子和单都相同
        if(injectionType == InjectionType.TypeSameTwoSingle) {
            if(opencode[0] == opencode[1] || opencode[1] == opencode[2]) {
                if(opencode[0] == number[0] && opencode[1] == number[1] && opencode[2] == number[2]) {
                    return fee * 80 * multi / 2;
                }
            }
            return 0;
        }
        //三不同号 要求开奖号码是三个不同的
        if(injectionType == InjectionType.TypeNoSameThree){
            if(opencode[0] == number[0] && opencode[1] == number[1] && opencode[2] == number[2]) {
                return fee * 40 * multi / 2;
            }
            return 0;
        }
        //二不同号 指定2个不同号码和一个任意号码
        if(injectionType == InjectionType.TypeNoSameTwo){
            if( (opencode[0] == number[0] && opencode[1] == number[1]) ||
            (opencode[1] == number[0] && opencode[2] == number[1]) ) {
                 return fee * 8 * multi / 2;
            }
            return 0;
        }
        //三连号 可能情况 123 234 345 456
        if(injectionType == InjectionType.TypeThreeConsecutive){
            if(opencode[1]-opencode[0] == 1 && opencode[2]-opencode[1] == 1){
                return fee * 10 * multi / 2;
            }
            return 0;
        }

        return 0;
    }
    //开盘函数
    function open() public {
        require(block.number > openBlock);//检查一下当前块数大于设置的可开盘块数

        uint8[] memory openCode; //openCode是存储开奖号码

        uint8 a = uint8(uint(block.blockhash(baseBlock + codeAOffset)) % 6 + 1); //将当前区块的hash值+codeAOffset,运算出一个hash值与6取模并且+1
        uint8 b = uint8(uint(block.blockhash(baseBlock + codeBOffset)) % 6 + 1); //将当前区块的hash值+codeBOffset,运算出一个hash值与6取模并且+1
        uint8 c = uint8(uint(block.blockhash(baseBlock + codeCOffset)) % 6 + 1); //将当前区块的hash值+codeCOffset,运算出一个hash值与6取模并且+1
        uint8 t;
        //将开奖号码排序
        if (a > b) {
            t = a;
            a = b;
            b = t;
        }
        if (b > c)
        {
            t = c;
            c = b;
            b = t;
        }
        if (a > b) {
            t = a;
            a = b;
            b = t;
        }
        OpenCode memory code = OpenCode(baseBlock, openCode); //新建一个代码号码记录
        code.opencode[0] = a; //将记录中的号码赋值
        code.opencode[1] = b;
        code.opencode[2] = c;
        // 记录中奖号码
        allOpenCodes.push(code);
        //计算奖金
        uint i = 0;
        uint bonusMoney; //记录该获得的奖金
        for(i=0; i<orders.length; ++i) { //循环遍历每一个竞猜用户
            Order memory o = orders[i]; //o是遍历的orders寄存器
            //uint8[] number,uint8 sumval, uint8 multi, InjectionType injectionType,uint8[] opencode
            bonusMoney = calcBonus(o.number, o.sumVal, o.multi, o.injectionType, code.opencode); //调用calcBonus函数取得应该获得的奖金给bonusMoney
            if (bonusMoney > 0){
                bonus[o.player] += bonusMoney; //奖金映射,将获得奖金映射到地址中
                totalBonus += bonusMoney; //增加总奖金金额
            }
        }
        uint bonusMoney = fee * 120;
        uint i = 0;
        for(i=0; i<orders.length; ++i) {
            Order memory o = orders[I];
            if(o.number[0] == code.opencode[0]
                && o.number[1] == code.opencode[1]
                && o.number[2] == code.opencode[2]) {

                bonus[o.player] += bonusMoney * o.multi;
                totalBonus += bonusMoney * o.multi;
            }
        }
        // 重置数据,开始下一轮竞猜
        delete orders;
        baseBlock = block.number;
        stopBlock = baseBlock + 30; // about 450 seconds
        openBlock = stopBlock + 10; // about 150 seconds

    }

    // 彩民获取奖金
    function withdraw() public {
        uint money = bonus[msg.sender]; //奖金寄存器
        require(money > 0); //检查money大于0

        delete bonus[msg.sender]; //清楚该地址映射
        totalBonus -= money; //总奖金金额减少
        msg.sender.transfer(money); //给该地址返还奖金
    }
}

中心服务器部分

node.js 服务器 安装初始化
npm init  
安装web3.js到项目中:
npm install web3 --save  
在服务器使用web3.js
在web3test目录下新建index.js文件,在其中输入以下代码:
 var Web3 = require("web3");  
 var web3 = new Web3();  
 var web3 = new Web3(new Web3.providers.HttpProvider("http://127.0.0.1:8545"));  
获取已部署的智能合约实例
    var abi = /*编译器生成的abi代码*/;
    var contractAddress = '/*这里是合约的地址*/';  
    var hello = new web3.eth.Contract(abi,address);
调用节点
var faq = web3.eth.getAccounts(function(error,result){
    if(!error)
    resp.send(result);
});
console.log(faq);
var daq=web3.eth.personal.newAccount("1234",function(error,result){
    if(!error)
    resp.send(result);
});
var faq=web3.eth.personal.unlockAccount("0xc40b465e28a386c56806058571d0baf303af079c","123",function(error,result){
    if(!error)
    resp.send(result);
});
    console.log(faq);
hello.methods.helloworld().call(function(error,result){
        if(!error)
        resp.send(result);
});
下面的是一个调用接受转账的函数,from是转账的账户,value是转账的数额,单位是wei,gas就是设定的gas,function是后面接的回调函数,回调函数的返回值是交易地址,还没有找到如何去查看函数的返回值。
hello.methods.hellomoney().send({from:"0xc40b465e28a386c56806058571d0baf303af079c",value: 200000000,gas:3000000},function(error,result){
        if(!error)
        resp.send(result);
});

hello.methods.helloPDJ().send({from:"0xc40b465e28a386c56806058571d0baf303af079c",gas:3000000},function(error,result){
if(!error)
resp.send(result);
})
上面调用会变更数据,但不接受转账的函数,from是发送调用的地址,
function是回调函数,回调函数的返回值是交易地址,还没有找到如何去查看函数的返回值。

上一篇 下一篇

猜你喜欢

热点阅读