4.1.1 语法介绍 block/msg/now
- block 在调用某个方法的时候,solidity 会提供一个block 的变量,把当前块的信息返回。
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 在调用某个方法的时候,会给方法传递一个msg的属性,用来传递消息
msg.data (bytes) 完整的 calldata
msg.gas (uint) 剩余gas
msg.sender (address) 该消息(当前呼叫)的发送者
msg.sig (bytes4) 呼叫数据的前四个字节(即功能标识符)
msg.value (uint) 发送的消息的数量
- now (uint) 当前块时间戳(block.timestamp的别名)
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);
- 调用合约
函数下面是调用一个只读函数,后面参数call是表明此调用不会产生数据更改,只会在一个节点上处理数据,也不会消耗gas,function是回调函数,回调函数的返回值就是函数的返回值。
下面是调用一个只读函数,后面参数call是表明此调用不会产生数据更改,只会在一个节点上处理数据,也不会消耗gas,function是回调函数,回调函数的返回值就是函数的返回值。
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是回调函数,回调函数的返回值是交易地址,还没有找到如何去查看函数的返回值。
网友评论