以太坊Dapp项目-拍卖网站-智能合约编写测试

news/2024/5/17 10:15:50/文章来源:https://blog.csdn.net/weixin_34387284/article/details/91785195
修订日期姓名邮箱
2018-10-18brucefengbrucefeng@brucefeng.com

前言

写这篇文章的初衷其实很简单,在MyEtherWallet上申请以太坊ENS的时候,竞标的以太币两次被吞,而且是在规定时间点进行了价格公告,这篇文章的设计思路其实就是跟ENS的竞标流程类似,希望对大家有所帮助,所以,准备写完之后,再重新去整一次ENS的申请,如果再被吞,我就要举报了:-),本文主要是本人用于项目整理,便于自己查询,不做任何商业用途。

现在回归到技术上来,这个项目其实涉及到蛮多的知识点的,是非常不错的以太坊智能合约以及Dapp学习项目,至少在目前而言,还没有看到特别好的学习项目被分享出来,通过该项目,我们可以掌握如下内容:

  • 以太坊智能合约编程语言Solidity的编写
  • 智能合约框架Truffle的学习与使用
  • 以太坊与IPFS的整合
  • NodeJS编程学习
  • 以太坊Web3JS的接口学习
  • Dapp与主流数据库的整合(本文为NoSQL类型的MongoDB)
  • 维克里拍卖法则

一.项目介绍

1.项目功能

(1)项目展示

允许商家列出项目,我们将为任何人建立免费列出项目的功能,我们会将这些项目都存储在区块链和非区块链的数据库中,方便查询。

(2) 文件存储

将文件添加到IPFS:我们将商品图像和商品描述(大文本)上传至IPFS的功能。

(3)浏览商品

我们将添加根据类别,拍卖时间等过滤和浏览商品的功能。

(4)商品拍卖

实现维克里密封拍卖,招标流程跟ENS类似。

(5)托管合约

一旦投标结束,商品有赢家,我们将在买方,卖方和第三方仲裁人之间创建一个托管合同

(6) 2-of-3数字签名

我们将通过2-of-3数字,其中3名参与者中的2名必须投票将资金释放给卖方或者将金额退还给卖方。

2.项目架构

以下图片来源于网络

以太坊Dapp项目-拍卖网站-智能合约编写测试

(1) Web前端

HTML,CSS,JavaScript(大量使用web3js),用户将通过这个前端应用程序与区块链,IPFS和NodeJS服务器进行交互

(2) 区块链

这是所有代码和交易所在的应用程序的核心,商店中所有商品,用户出价和托管都写在区块链上。

(3) NodeJS服务器

这是前端通过其与数据库进行通信的后端服务器,我们将公开一些简单的API来为前端查询和从数据库中检索商品。

(4) MongoDB

尽管商品存储在区块链中,但是查询区块链展示商品和应用各种过滤器(仅显示特定类别的商品,显示即将过期的商品等)效率并不高,我们将使用MongoDB数据库来存储商品信息并查询它以展示商品。

(5)区块链存储IPFS

当用户在商店中列出商品时,前端会将商品文件和描述上传至IPFS,并将上传文件的散列HASH存储到区块链中。

3. 业务流向

以太坊Dapp项目-拍卖网站-智能合约编写测试

(1) 用户访问前端

(2) 将商品文件与描述信息传至IPFS中

(3) IPFS返回对应的Hash值

(4) 网页前端调用合约将Hash值结合产品ID,拍卖时间,分类,价格等写入区块链中

(5) 从区块链中读取数据展示在web前端

(6) NodeJs服务器监听这些事件,当事件被合约触发时,服务器从区块链中取出数据缓存至mongodb中。

4. 实现步骤

  • 先通过truffle 和 solidity实现合约代码,将其部署到truffle develop自带的测试网络中,并且在truffle console中可以自由交互。

  • 通过命令行安装并与IPFS交互

  • 在后端实现完成后,我们将构建Web前端以与合约和IPFS进行交互,我们也会实现招标,揭示前端的拍卖功能。

  • 我们将安装MongoDB并设计数据结构来存储商品

  • 数据库启动并允许后,我们将实现监听合约时间的NodeJS服务端代码,并将请求记录到控制台,然后我们将执行代码将商品插入数据库中。

  • 我们将更新到我们的前端,从数据库而不是区块链中查找商品(如何保证数据库中的数据不被篡改?)

  • 我们将实现托管合同和相应的前端,参与者可以向买方/卖方发放或退款。

二.初始化项目环境

1.Truffle初识与安装

(1) Truffle简介

Truffle是针对基于以太坊的Solidity语言的一套开发框架。本身基于Javascript,相比于没有框架编写Solidity智能合约,Truffle提供了如下功能

  • 首先对客户端做了深度集成。开发,测试,部署一行命令都可以搞定。不用再记那么多环境地址,繁重的配置更改,及记住诸多的命令。
  • 它提供了一套类似mavengradle这样的项目构建机制,能自动生成相关目录,默认是基于Web的。
  • 简化开发流程:提供了合约抽象接口,可以直接通过合约.deployed()方法拿到合约对象,在Javascript中直接操作对应的合约函数。原理是使用了基于web3.js封装的Ether Pudding工具包。
  • 提供了控制台,使用框架构建后,可以直接在命令行调用输出结果,可极大方便开发调试(这一点有点不敢过于恭维,不少时候在调试的时候还不如Remix)
  • 提供了监控合约,配置变化的自动发布,部署流程。不用每个修改后都重走整个流程。

关于其相关介绍,可以直接到Truffle官网进行了解。

(2) Truffle安装

安装Truffle非常简单,官网上面也非常简单明了

$ npm install truffle -g

同样的,本文只写相关相关的内容与步骤,此处不做过多扩展,移步官方文档查看更多的内容。

2.创建项目目录

$ mkdir auctionDapp/ ; cd auctionDapp
$ truffle unbox webpack

创建项目目录`auctionDapp,并进行初始化工作,返回如下信息则表示truffle项目框架搭建完毕

以太坊Dapp项目-拍卖网站-智能合约编写测试

.
├── LICENSE
├── app  //前端设计
├── box-img-lg.png
├── box-img-sm.png
├── build //智能合约编译后文件存储路径
├── contracts //智能合约文件存储路径
├── migrations //存放发布脚本文件
├── node_modules //相关nodejs库文件
├── package-lock.json 
├── package.json //安装包信息配置文件
├── test //合约测试文件存放路径
├── truffle.js // truffle配置文件
└── webpack.config.js // webpack配置文件

将用于测试的智能合约删除,避免干扰我们的项目。

$ rm -rf contracts/{ConvertLib.sol,MetaCoin.sol}

(1) Truffle Box用途

提到Box,作为蓝鲸智云的忠实粉丝与早期布道者,有必要提一下蓝鲸MagicBox,那是一个专门提供给运维开发人员的前端框架集合,这里的box也是类似的用途,官网是这么描述的

TRUFFLE BOXES
THE EASIEST WAY TO GET STARTED
Truffle Boxes are helpful boilerplates that allow you to focus on what makes your dapp unique. In addition to Truffle, Truffle Boxes can contain other helpful modules, Solidity contracts & libraries, front-end views and more; all the way up to complete example dapps.

简而言之,TRUFFLE BOXES就是将solidity智能合约,相关库,前端框架都集成在一起的集合,方便开发人员在最大程度上简化不必要的环境搭建与技术选型工作。

以太坊Dapp项目-拍卖网站-智能合约编写测试

(2) Webpack框架

Webpack 是一个前端资源加载/打包工具。它将根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源。

以太坊Dapp项目-拍卖网站-智能合约编写测试

从图中我们可以看出,Webpack 可以将多种静态资源 js、css等转换成一个静态文件,减少了页面的请求。

三.编写测试智能合约

1.定义结构体

本章节定义了一个名为AuctionStore的合约,定义了枚举变量ProductStatus用于区分商品竞拍的阶段,定义枚举变量ProductCondition用于标识拍卖商品是新品还是二手商品,为了便于统计商品数量,我们定义了uint类型变量productIndex通过递增的方式存储商品数量,在商品发布之后会形成两个字典表。

  • 产品Id与钱包地址对应表productIdInStore(多对一)
产品ID发布者钱包地址
10x627306090abab3a6e1400e9345bc60c78a8bef57
20xf17f52151ebef6c7334fad080c5704d77216b732
30xf17f52151ebef6c7334fad080c5704d77216b732
40x627306090abab3a6e1400e9345bc60c78a8bef57
50xc5fdf4076b8f3a5357c5e395ab970b5b54098fef

如上表

产品ID(1,4)的发布者为0x627306090abab3a6e1400e9345bc60c78a8bef57

产品ID(2,3)的发布者为0xf17f52151ebef6c7334fad080c5704d77216b732

产品ID为5的发布者为0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef

  • 钱包地址与商品对应表(一对多)stores
发布者钱包地址产品ID商品对象
0x627306090abab3a6e1400e9345bc60c78a8bef571如"Macbook Pro 2016"
0x627306090abab3a6e1400e9345bc60c78a8bef574如"IPhone 8 Plus"
0xf17f52151ebef6c7334fad080c5704d77216b7322如"IPhone X"
0xf17f52151ebef6c7334fad080c5704d77216b7323如"Macbook Pro 2017"
0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef5如"Surface Pro4"

代码中定义了投标人结构体Bid,主要保存其投标人钱包地址竞标的产品ID竞标价(虚价)是否揭标,并将其字典映射作为属性放入商品结构体Product中,关于商品结构体Product的相关说明参考代码中注释即可。

pragma solidity ^0.4.24;
//定义合约AuctionStore
contract AuctionStore {//定义枚举ProductStatusenum ProductStatus {Open, //拍卖开始Sold, //已售出,交易成功Unsold //为售出,交易未成功}enum ProductCondition {New, //拍卖商品是否为新品Used //拍卖商品是否已经使用过}// 用于统计商品数量,作为IDuint public productIndex; //产品Id与钱包地址的对应关系mapping(uint => address) productIdInStore;// 通过地址查找到对应的商品集合mapping(address => mapping(uint => Product)) stores;//增加投标人信息struct Bid {address bidder;uint productId;uint value;bool revealed; //是否已经揭标}struct Product {uint id;                 //产品idstring name;             //商品名称string category ;       //商品分类string imageLink ;       //图片Hashstring descLink;        // 图片描述信息的Hashuint auctionStartTime; //开始竞标时间uint auctionEndTime;    //竞标结束时间uint startPrice;       //拍卖价格   address highestBidder ; //出价最高,赢家的钱包地址uint highestBid ;       //赢家得标的价格uint secondHighestBid ; //竞标价格第二名uint totalBids ;        //共计竞标的人数ProductStatus status;    //状态ProductCondition condition ;  //商品新旧标识mapping(address => mapping(bytes32 => Bid)) bids;// 存储所有投标人信息}constructor ()public{productIndex = 0;}}

2. 实现添加商品

我们开始实现拍卖商品的发布操作,需要保证传入的商品拍卖开始时间不能晚于结束时间,当商品被添加后,统计商品的索引ID自增,根据传入的商品属性创建商品product对象,将该对象存入stores中,发布者钱包地址为msg.sender(可以通过from参数传入), 产品ID为当前productIndex的值,同时将数据存入productIdInStore中,Index为当前productIndex的值,Valuemsg.sender,通过该方法可以实现

  • productIndex自增1
  • productIdInStore添加数据
  • stores添加数据
 //实现添加商品到区块链function addProductToStore(string _name, string _category, string _imageLink, string _descLink, uint _auctionStartTime, uint _auctionEndTime ,uint _startPrice, uint  _productCondition) public  {//开始时间需要小于结束时间require(_auctionStartTime < _auctionEndTime,"开始时间不能晚于结束时间");//商品索引ID自增productIndex += 1;//product对象稍后直接销毁,类型为memory即可Product memory product = Product(productIndex,_name,_category,_imageLink,_descLink,_auctionStartTime,_auctionEndTime,_startPrice,0,0,0,0,ProductStatus.Open,ProductCondition(_productCondition));stores[msg.sender][productIndex] = product;productIdInStore[productIndex] = msg.sender;   }

3. 读取商品信息

在实现对拍卖商品信息进行读取的时候,我们只需要通过其productIdproductIdInStore中获取发布者地址,通过发布者地址bidderproductIdstores中获取到product对象,从而获取该对象的相关属性信息。

//通过产品ID读取商品信息function getProduct(uint _productId)  public view returns (uint,string, string,string,string,uint ,uint,uint, ProductStatus, ProductCondition)  {Product memory product = stores[productIdInStore[_productId]][_productId];return (product.id, product.name,product.category,product.imageLink,product.descLink,product.auctionStartTime,product.auctionEndTime,product.startPrice,product.status,product.condition);}

4. 商品投标操作

商品发布好之后,我们需要在规定的时间段内进行商品投标操作,也就是竞标,首先需要满足几个前提

  • 当前时间不能早于商品竞拍开始时间
  • 当前时间不能晚于商品竞拍结束时间
  • 设置的虚拟价格不能低于开标价格

参考读取商品信息getProduct方法,通过竞标方法传入的productId获取到product对象,将Bid对象存入product对象中,其中传入的加密参数bid是通过加密函数对实际竞标价格+揭标密钥进行加密后得到的,同时将竞标人数递增1。

 //投标,传入参数为产品Id以及Hash值(实际竞标价与秘钥词语的组合Hash),需要添加Payablefunction bid(uint _productId, bytes32 _bid) payable public returns (bool) {Product storage product = stores[productIdInStore[_productId]][_productId];require(now >= product.auctionStartTime, "商品竞拍时间未到,暂未开始,请等待...");require(now <= product.auctionEndTime,"商品竞拍已经结束");require(msg.value >= product.startPrice,"设置的虚拟价格不能低于开标价格");require(product.bids[msg.sender][_bid].bidder == 0); //在提交竞标之前,必须保证bid的值为空//将投标人信息进行保存product.bids[msg.sender][_bid] = Bid(msg.sender, _productId, msg.value,false);//商品投标人数递增product.totalBids += 1;//返回投标成功return true;}

5.公告价格揭标

本文提到的价格公告跟揭标属于同一个概念,只是在使用的时候根据语境进行了相应的调整。

在竞标结束后,竞标人需要进行价格公告,核心在于竞标人传入的实际竞标价_amount与揭标密钥_secret的加密Hash值需要与上文的加密Hashbid要一致,否则会找不到对应的钱包地址,同时要保证该账户之前并未进行价格揭标操作。

以太坊Dapp项目-拍卖网站-智能合约编写测试

//公告,揭标方法function revealBid(uint _productId, string _amount, string _secret) public {//确保当前时间大于投标结束时间require(now > product.auctionEndTime,"竞标尚未结束,未到公告价格时间");// 对竞标价格与竞价密钥进行加密bytes32 sealedBid = keccak256(_amount,_secret);//通过产品ID获取商品信息Product storage product = stores[productIdInStore[_productId]][_productId];//获取投标人信息Bid memory bidInfo = product.bids[msg.sender][sealedBid];//判断是否存在钱包地址,钱包地址0x4333  uint160的钱包类型require(bidInfo.bidder > 0,"该账户未在竞标者信息中"); //判断该账户是否已经揭标过require(bidInfo.revealed == false,"该账户已经揭标");// 定义系统的退款uint refund;uint amount = stringToUint(_amount);// bidInfo.value是在竞标时候定义的虚价,通过msg.value设置。if (bidInfo.value < amount) { //如果bidInfo.value的值< 实际竞标价,则返回全部退款,属于无效投标refund = bidInfo.value;}else { //如果属于有效投标,参照如下分类if (address(product.highestBidder) == 0) { //第一个参与公告的人,此时该值为0//将出标人的地址赋值给最高出标人地址product.highestBidder = msg.sender;// 将出标人的价格作为最高价格product.highestBid = amount;// 将商品的起始拍卖价格作为第二高价格product.secondHighestBid = product.startPrice;// 将多余的钱作为退款,如bidInfo.value = 20,amount = 12,则退款8refund = bidInfo.value - amount;}else { //此时参与者不是第一个参与公告的人// amount = 15 , bidInfo.value = 25,amount > 12 if (amount > product.highestBid) {// 将原来的最高价赋值给第二高价product.secondHighestBid = product.highestBid;// 将原来最高的出价退给原先的最高价地址product.highestBidder.transfer(product.highestBid);// 将当前出价者的地址作为最高价地址product.highestBidder = msg.sender;// 将当前出价作为最高价,为15product.highestBid = amount;// 此时退款为 20 - 15 = 5refund = bidInfo.value - amount;}else if (amount > product.secondHighestBid) {//将当前竞标价作为第二高价格product.secondHighestBid = amount;//退还所有竞标款refund = amount;}else { //如果出价比第二高价还低的话,直接退还竞标款refund = amount;}}if (refund > 0){ //取回退款msg.sender.transfer(refund);product.bids[msg.sender][sealedBid].revealed = true;}}}

此处的transfer不是常规的转账,可以理解为退款

6.相关帮助方法

    //1. 获取竞标赢家信息function highestBidderInfo (uint _productId)public view returns (address, uint ,uint) {Product memory product = stores[productIdInStore[_productId]][_productId];return (product.highestBidder,product.highestBid,product.secondHighestBid);}    //2. 获取参与竞标的人数function  totalBids(uint _productId) view public returns (uint) {Product memory product = stores[productIdInStore[_productId]][_productId];return  product.totalBids;}//3. 将字符串string到uint类型function stringToUint(string s) pure private returns (uint) {bytes memory b = bytes(s);uint result = 0 ;for (uint i = 0; i < b.length; i++ ){if (b[i] >=48 && b[i] <=57){result = result * 10  + (uint(b[i]) - 48);}}return result;}

7.合约完整代码

pragma solidity ^0.4.24;
//定义合约AuctionStore
contract AuctionStore {//定义枚举ProductStatusenum ProductStatus {Open, //拍卖开始Sold, //已售出,交易成功Unsold //为售出,交易未成功}enum ProductCondition {New, //拍卖商品是否为新品Used //拍卖商品是否已经使用过}// 用于统计商品数量,作为IDuint public productIndex; //商品Id与钱包地址的对应关系mapping(uint => address) productIdInStore;// 通过地址查找到对应的商品集合mapping(address => mapping(uint => Product)) stores;//增加投标人信息struct Bid {address bidder;uint productId;uint value;bool revealed; //是否已经揭标}//定义商品结构体struct Product {uint id;                 //商品idstring name;             //商品名称string category ;       //商品分类string imageLink ;       //图片Hashstring descLink;        // 图片描述信息的Hashuint auctionStartTime; //开始竞标时间uint auctionEndTime;    //竞标结束时间uint startPrice;       //拍卖价格   address highestBidder ; //出价最高,赢家的钱包地址uint highestBid ;       //赢家得标的价格uint secondHighestBid ; //竞标价格第二名uint totalBids ;        //共计竞标的人数ProductStatus status;    //状态ProductCondition condition ;  //商品新旧标识mapping(address => mapping(bytes32 => Bid)) bids;// 存储所有投标人信息}constructor ()public{productIndex = 0;}//添加商品到区块链中function addProductToStore(string _name, string _category, string _imageLink, string _descLink, uint _auctionStartTime, uint _auctionEndTime ,uint _startPrice, uint  _productCondition) public  {//开始时间需要小于结束时间require(_auctionStartTime < _auctionEndTime,"开始时间不能晚于结束时间");//商品ID自增productIndex += 1;//product对象稍后直接销毁即可Product memory product = Product(productIndex,_name,_category,_imageLink,_descLink,_auctionStartTime,_auctionEndTime,_startPrice,0,0,0,0,ProductStatus.Open,ProductCondition(_productCondition));stores[msg.sender][productIndex] = product;productIdInStore[productIndex] = msg.sender;   }//通过商品ID读取商品信息function getProduct(uint _productId)  public view returns (uint,string, string,string,string,uint ,uint,uint, ProductStatus, ProductCondition)  {Product memory product = stores[productIdInStore[_productId]][_productId];return (product.id, product.name,product.category,product.imageLink,product.descLink,product.auctionStartTime,product.auctionEndTime,product.startPrice,product.status,product.condition);}//投标,传入参数为商品Id以及Hash值(实际竞标价与秘钥词语的组合Hash),需要添加Payablefunction bid(uint _productId, bytes32 _bid) payable public returns (bool) {Product storage product = stores[productIdInStore[_productId]][_productId];require(now >= product.auctionStartTime, "商品竞拍时间未到,暂未开始,请等待...");require(now <= product.auctionEndTime,"商品竞拍已经结束");require(msg.value >= product.startPrice,"设置的虚拟价格不能低于开标价格");require(product.bids[msg.sender][_bid].bidder == 0); //在提交竞标之前,必须保证bid的值为空//将投标人信息进行保存product.bids[msg.sender][_bid] = Bid(msg.sender, _productId, msg.value,false);//商品投标人数递增product.totalBids += 1;//返回投标成功return true;}//公告,揭标方法function revealBid(uint _productId, string _amount, string _secret) public {//通过商品ID获取商品信息Product storage product = stores[productIdInStore[_productId]][_productId];//确保当前时间大于投标结束时间require(now > product.auctionEndTime,"竞标尚未结束,未到公告价格时间");// 对竞标价格与关键字密钥进行加密bytes32 sealedBid = keccak256(_amount,_secret);//获取投标人信息Bid memory bidInfo = product.bids[msg.sender][sealedBid];//判断是否存在钱包地址,钱包地址0x4333  uint160的钱包类型require(bidInfo.bidder > 0,"钱包地址不存在"); //判断是否已经公告揭标过require(bidInfo.revealed == false,"已经揭标");// 定义系统的退款uint refund;uint amount = stringToUint(_amount);// bidInfo.value 其实就是 mask bid,用于迷惑竞争对手的价格if (bidInfo.value < amount) { //如果bidInfo.value的值< 实际竞标价,则返回全部退款,属于无效投标refund = bidInfo.value;}else { //如果属于有效投标,参照如下分类if (address(product.highestBidder) == 0) { //第一个参与公告的人,此时该值为0//将出标人的地址赋值给最高出标人地址product.highestBidder = msg.sender;// 将出标人的价格作为最高价格product.highestBid = amount;// 将商品的起始拍卖价格作为第二高价格product.secondHighestBid = product.startPrice;// 将多余的钱作为退款,如bidInfo.value = 20,amount = 12,则退款8refund = bidInfo.value - amount;}else { //此时参与者不是第一个参与公告的人// amount = 15 , bidInfo.value = 25,amount > 12 if (amount > product.highestBid) {// 将原来的最高价地址 赋值给 第二高价的地址product.secondHighestBid = product.highestBid;// 将原来最高的出价退还给原先退给原先的最高价地址product.highestBidder.transfer(product.highestBid);// 将当前出价者的地址作为最高价地址product.highestBidder = msg.sender;// 将当前出价作为最高价,为15product.highestBid = amount;// 此时退款为 20 - 15 = 5refund = bidInfo.value - amount;}else if (amount > product.secondHighestBid) {//product.secondHighestBid = amount;//退还所有竞标款refund = amount;}else { //如果出价比第二高价还低的话,直接退还竞标款refund = amount;}}if (refund > 0){ //退款msg.sender.transfer(refund);product.bids[msg.sender][sealedBid].revealed = true;}}}//帮助方法//1. 获取竞标赢家信息function highestBidderInfo (uint _productId)public view returns (address, uint ,uint) {Product memory product = stores[productIdInStore[_productId]][_productId];return (product.highestBidder,product.highestBid,product.secondHighestBid);}    //2. 获取参与竞标的人数function  totalBids(uint _productId) view public returns (uint) {Product memory product = stores[productIdInStore[_productId]][_productId];return  product.totalBids;}//3. 将字符串string到uint类型function stringToUint(string s) pure private returns (uint) {bytes memory b = bytes(s);uint result = 0 ;for (uint i = 0; i < b.length; i++ ){if (b[i] >=48 && b[i] <=57){result = result * 10  + (uint(b[i]) - 48);}}return result;}
} 

8.合约测试

(1) 启动测试终端

$ truffle  develop

以太坊Dapp项目-拍卖网站-智能合约编写测试

(2) 编译合约

以太坊Dapp项目-拍卖网站-智能合约编写测试

此处Warning警告信息忽略即。

(3) 部署合约

以太坊Dapp项目-拍卖网站-智能合约编写测试

(4) 安装依赖库

安装ethereumjs-util,加密方法需要调用该库

$ npm install ethereumjs-util

(5) 查询测试账户

truffle(develop)> web3.eth.getBalance(web3.eth.accounts[1])
BigNumber { s: 1, e: 20, c: [ 1000000 ] }
truffle(develop)> web3.eth.getBalance(web3.eth.accounts[2])
BigNumber { s: 1, e: 20, c: [ 1000000 ] 
truffle(develop)> web3.eth.getBalance(web3.eth.accounts[3])
BigNumber { s: 1, e: 20, c: [ 1000000 ] 

查询用于测试的账户(竞标账户)的原始额度,均为100000.

(6) 商品发布

  • 初始化竞标价格
truffle(develop)> auctionAmount = web3.toWei(1,'ether')
'1000000000000000000'
  • 获取当前时间
truffle(develop)> auctionStartTime = Math.round(new Date() / 1000);  
1539885333
  • 调用发布合约
truffle(develop)> AuctionStore.deployed().then(function(i) {i.addProductToStore('Macbook Pro 2018 001','Phones &  Computers','imagesLink','descLink',auctionStartTime,auctionStartTime + 300,auctionAmount,0).then(function(f) {console.log(f)})});

竞标时间设置为5分钟

以太坊Dapp项目-拍卖网站-智能合约编写测试

(7) 查看相关参数

  • 查看商品个数
truffle(develop)> AuctionStore.deployed().then(function(i) {i.productIndex.call().then(function(f) {console.log(f)})})

以太坊Dapp项目-拍卖网站-智能合约编写测试

  • 查看商品信息
truffle(develop)> AuctionStore.deployed().then(function(i) {i.getProduct.call(1).then(function(f) {console.log(f)})})

以太坊Dapp项目-拍卖网站-智能合约编写测试

获取合约的方式还有:

truffle(develop)>var instance

truffle(develop)> instance = AuctionStore.deployed().then((i => {instance = i}))

truffle(develop)> instance.productIndex();

BigNumber { s: 1, e: 0, c: [ 1 ] }

(8) 开始竞标

务必在竞标结束时间前完成竞标操作

  • 对实际出标价与揭标密钥进行加密

[1] 导入加密库

truffle(develop)> EjsUtil = require('ethereumjs-util') 

[2] 进行加密

truffle(develop)> sealedBid1 = '0x' + EjsUtil.keccak256(2*auctionAmount + 'firstsecrt').toString('hex') 
'0xb0d5a0c4d195f138442910cd2ccd16da585784a24482f7e320f48d850e0fb86d'
truffle(develop)> sealedBid2 = '0x' + EjsUtil.keccak256(3*auctionAmount + 'secondsecrt').toString('hex') 
'0x9566873896902aca059cbe402b2aa82638fe6e57980c97ac25c576cc6496a233'
truffle(develop)> sealedBid3 = '0x' + EjsUtil.keccak256(4*auctionAmount + 'threesecrt').toString('hex') 
'0x79e5fcbcc9065408e06f20d224c7183d82089e0fbe8e344446b5f4527b5d2f4f'
  • 账户1参与竞标

实际amount = 2 auctionAmount , Mask BId: 2.5 *auctionAmount

truffle(develop)> AuctionStore.deployed().then(function(i){i.bid(1,sealedBid1,{value:2.5*auctionAmount,from:web3.eth.accounts[1]}).then(function(f) {console.log(f)})})

以太坊Dapp项目-拍卖网站-智能合约编写测试

truffle(develop)> web3.eth.getBalance(web3.eth.accounts[1])
BigNumber { s: 1, e: 19, c: [ 974888, 13600000000000 ] }
  • 账户2参与竞标

实际amount =3 * auctionAmount , Mask BId: 3.5 *auctionAmount

truffle(develop)> AuctionStore.deployed().then(function(i){i.bid(1,sealedBid2,{value:3.5*auctionAmount,from:web3.eth.accounts[2]}).then(function(f) {console.log(f)})})

以太坊Dapp项目-拍卖网站-智能合约编写测试

truffle(develop)> web3.eth.getBalance(web3.eth.accounts[1])
BigNumber { s: 1, e: 19, c: [ 974888, 13600000000000 ] }
truffle(develop)> web3.eth.getBalance(web3.eth.accounts[2])
BigNumber { s: 1, e: 19, c: [ 964903, 13600000000000 ] }
  • 账户3参与竞标

实际amount = 4 * auctionAmount , Mask BId: 4.5 *auctionAmount

truffle(develop)> AuctionStore.deployed().then(function(i){i.bid(1,sealedBid3,{value:4.5*auctionAmount,from:web3.eth.accounts[3]}).then(function(f) {console.log(f)})})

以太坊Dapp项目-拍卖网站-智能合约编写测试

truffle(develop)> web3.eth.getBalance(web3.eth.accounts[1])
BigNumber { s: 1, e: 19, c: [ 974888, 13600000000000 ] } //扣除2.5ether以及部分gas
truffle(develop)> web3.eth.getBalance(web3.eth.accounts[2])
BigNumber { s: 1, e: 19, c: [ 964903, 13600000000000 ] }//扣除3.5ether以及部分gas
truffle(develop)> web3.eth.getBalance(web3.eth.accounts[3])
BigNumber { s: 1, e: 19, c: [ 954903, 13600000000000 ] } //扣除4.5ether以及部分gas

(9) 公告揭标

时间必须超过竞标结束时间才能执行合约,揭标时需要填写实际竞标价

  • 账户1进行揭标
truffle(develop)> AuctionStore.deployed().then(function(i) {i.revealBid(1,(2*auctionAmount).toString(),'firstsecrt',{from: web3.eth.accounts[1]}).then(function(f){console.log(f)})});

以太坊Dapp项目-拍卖网站-智能合约编写测试

truffle(develop)> web3.eth.getBalance(web3.eth.accounts[1])
BigNumber { s: 1, e: 19, c: [ 979711, 68300000000000 ] }//观察变化
truffle(develop)> web3.eth.getBalance(web3.eth.accounts[2])
BigNumber { s: 1, e: 19, c: [ 964903, 13600000000000 ] }
truffle(develop)> web3.eth.getBalance(web3.eth.accounts[3])
BigNumber { s: 1, e: 19, c: [ 954903, 13600000000000 ] }
  • 账户2进行揭标
truffle(develop)> AuctionStore.deployed().then(function(i) {i.revealBid(1,(3*auctionAmount).toString(),'secondsecrt',{from: web3.eth.accounts[2]}).then(function(f){console.log(f)})});

以太坊Dapp项目-拍卖网站-智能合约编写测试

truffle(develop)>  web3.eth.getBalance(web3.eth.accounts[1])
BigNumber { s: 1, e: 19, c: [ 999711, 68300000000000 ] } //观察变化
truffle(develop)> web3.eth.getBalance(web3.eth.accounts[2])
BigNumber { s: 1, e: 19, c: [ 969815, 53800000000000 ] } //观察变化
truffle(develop)> web3.eth.getBalance(web3.eth.accounts[3])
BigNumber { s: 1, e: 19, c: [ 954903, 13600000000000 ] }
  • 账户3进行揭标
truffle(develop)> AuctionStore.deployed().then(function(i) {i.revealBid(1,(4* auctionAmount).toString(),'threesecrt',{from: web3.eth.accounts[3]}).then(function(f){console.log(f)})});

以太坊Dapp项目-拍卖网站-智能合约编写测试

truffle(develop)> web3.eth.getBalance(web3.eth.accounts[1])
BigNumber { s: 1, e: 19, c: [ 999711, 68300000000000 ] }
truffle(develop)> web3.eth.getBalance(web3.eth.accounts[2])
BigNumber { s: 1, e: 19, c: [ 999815, 53800000000000 ] }
truffle(develop)> web3.eth.getBalance(web3.eth.accounts[3])
BigNumber { s: 1, e: 19, c: [ 959815, 60200000000000 ] }

(10) 查看赢家信息

truffle(develop)> AuctionStore.deployed().then(function(i){i.highestBidderInfo.call(1).then(function(f){console.log(f)})});

以太坊Dapp项目-拍卖网站-智能合约编写测试

9.余额变化表

操作账户1账户2账户3
初始余额101010
开始竞标---
实际竞标价格234
对外虚拟价格2.53.54.5
账户余额9.748889.649039.54903
账户1开始揭标---
揭标结果最高价(退款为2.5-2)--
揭标余额9.797119.649039.54903
账户2开始揭标---
揭标结果出局(退款为实际竞标价2)最高价(退款为2.5-2)-
揭标余额9.99711969815-
账户3开始揭标---
揭标结果出局(不变)出局(退款为实际竞标价3)最高价(退款为4.5-4)
揭标余额9.997119.998159.59815

由于时间问题,本文先介绍拍卖网站的智能合约部分,其他内容会根据后续时间安排考虑再完善,感谢理解与支持!

转载于:https://blog.51cto.com/clovemfong/2306741

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_807892.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

RXThink 官方网站正式上线

百度智能云域名服务&#xff0c;.com新用户首购仅需25元 RXThink 是一个开源的权限及内容管理框架&#xff0c;提供更方便、更安全的 WEB 应用开发体验&#xff0c;采用了全新的架构设计和命名空间机制&#xff0c;融合了模块化、驱动化和插件化的设计理念于一体&#xff0c;…

利用新浪云SAE搭建可访问的免费个人网站

注&#xff1a;老师课件中&#xff0c;提到我们应该把网站发布到 http://www.openshift.com , 这是Red-hat 开发的PAAS服务&#xff0c;即是包装好底层的硬件和基础软件&#xff08;如 apache、nginx 、mysql 、php&#xff09;等服务&#xff0c;使用者可以直接在上面发布应用…

新浪云SAE搭建可访问的免费个人网站

注&#xff1a;老师课件中&#xff0c;提到我们应该把网站发布到 http://www.openshift.com , 这是Red-hat 开发的PAAS服务&#xff0c;即是包装好底层的硬件和基础软件&#xff08;如 apache、nginx 、mysql 、php&#xff09;等服务&#xff0c;使用者可以直接在上面发布应用…

php电商网站源码_PHP新款美化ui大学校园表白墙网站源码

源码描述&#xff1a;花十分钟搭建一款学校的表白校园墙程序修复一些小问题&#xff0c;安装很简单&#xff0c;内附安装教程说明安装方法&#xff1a;1、上传源码到主机或服务器2丶导入数据库3、 修改数据库配置地址&#xff0c;\inc\config.php后台账号&#xff1a;admin后台…

如何把不同尺寸ico格式图标_网站ico 图标不会做?用这个小工具轻松搞定

给自己的网站加上一个小图标&#xff0c;显示既专业又美观。网站左上角显示的小图标一般是ico格式的图片&#xff0c;然后在HTML里使用代码去设置ico小图片为该网站的小标。<link rel"shortcut icon" href"/favicon.ico" type"image/x-icon"/…

九天自助建站系统_自助建站系统做网站简单吗?广州有哪些网站建设

自助建站系统是根据传统建站技术&#xff0c;进行升级把开发技术简化&#xff0c;把传统建站高技术门槛降为“零”&#xff0c;使第一次用自助建站系统的用户&#xff0c;也可以轻松简单完成建站。我们继续往下聊一下&#xff0c;详细拆分自助建站系统&#xff0c;到底建站简单…

jupyter不能用linux命令,jupyter网站的使用以及常见linux下的简单的命令

python下安装jupyter常识&#xff1a;linux下查看计算机的ip代码是&#xff1a;ifconfig&#xff1b;ping IP;用户名查看&#xff1a;uname;文件传输&#xff1a;sftp 用户名对方 IP;SSH链接计算机的命令&#xff1a;ssh yuan对方IP&#xff0c;接下来再输入对方计算机passwd1、…

网站设计常用技巧收集

C#论坛同步地址&#xff1a;http://www.cckan.net/thread-1087-1-1.html 先说一下写这篇文章的目的吧&#xff0c;这上面的东西很多不是我写的&#xff0c;也不是我总结的&#xff0c;这点我肯定&#xff0c;呵呵&#xff0c;我希望大家也能提提你自己的建议&#xff0c;希望不…

传统网站与Web标准——DIV+CSS布局实例

主要内容&#xff1a; “结构与表现分离”的设计思想纵向导航条与横向导航条的切换【步骤1】 一、效果 二、HTML <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <…

酷站欣赏:分享13个五彩缤纷的国外网站作品

对于网页设计来说&#xff0c;使用正确的调色板可能是设计过程中最重要的部分&#xff0c;也是最具挑战性的之一。虽然现有的品牌通常对所用颜色的影响最大&#xff0c;但其他因素可以发挥作用。这篇文章&#xff0c;我们已经收集各种使用颜色的的网站作品&#xff0c;为您提供…

python获取get请求的耗时时间_python爬取网站数据四种姿势,你值得拥有

前言首先&#xff0c;分析来爬虫的思路&#xff1a;先在第一个网页(https://www.wikidata.org/w/index.php?titleSpecial:WhatLinksHere/Q5&limit500&from0)中得到500个名人所在的网址&#xff0c;接下来就爬取这500个网页中的名人的名字及描述&#xff0c;如无描述&a…

python selenium爬虫需要账号和密码登陆的网页_Python爬虫——selenium模拟京东网站登录(一)...

1.导入需要的库import timefrom selenium import webdriver2.浏览器获取驱动需要下载跟chrome浏览器相匹配的驱动driverchrome.exe&#xff0c;详情见&#xff1a;根据电脑浏览器的版本下载相应的驱动chromedriver.exe&#xff0c;环境变量的配置&#xff0c;详情见这里Window …

基于JSP开发的旅游网站管理系统

10092基于JSP开发的旅游网站管理系统 代码&#xff1a; 鏈-椄&#xff1a;https://panbaiducom/s/11qEwMtFAmmwMDLQO_Pbk2Q &#xff08;把换成 . 就可正常访问&#xff09; 趧-紶-碼&#xff1a;6886 f/u枝此段-吶傛打开baidu網盤手机App&#xff0c;caozuo更方便哦 技术 JA…

基于java的心理健康网站系统设计与实现

10001-心理健康测评网站 开发工具 eclipse tomact mysql jdk 功能详情

基于SSH开发的在线教学网站 java mysql

基于SSH开发的在线教学网站 技术 Spring Struts Hibernate 工具 eclipse tomact mysql jdk 功能详情 前台功能&#xff1a;后台功能&#xff1a;网站首页系统属性我的信息修改密码教学课件系统管理教学视频教学管理考试试题课程管理在线自测学生管理留言板讨论管理进入…

基于SpringBoot的招生咨询网站

10190_基于SpringBoot的招生咨询网站 技术 SpringBoot 工具 eclipse tomcat mysql jdk 功能详情 前台&#xff1a; 登录注册、录取信息查询、招生计划、专业查询、年历信息、问题解答、招生论坛 后台&#xff1a;专业管理、招生计划、系统管理、问题解答、信息管理、录取…

亿级流量网站架构核心技术 跟开涛学搭建高可用高并发系统

亿级流量网站架构核心技术 跟开涛学搭建高可用高并发系统1.高并发原则1.1 无状态1.2 拆分1.3 服务化1.4 消息队列1.5 数据异构1.6 缓存银弹1.7 并发化2 高可用原则2.1 降级2.2 限流2.3 切流量2.4 可回滚3 业务设计原则3.1 防重设计3.2 幂等设计3.3 流程可定义3.4 状态与状态机…

html如何访问外部网站,利用Github部署外部可访问的H5网站(无需服务器)

没有服务器如何外部访问你的HTML 本文是借助GitHub 实现 一半测试或者做个小的静态页面还是可以的。 下文是转载(像素级复制)亲测有效&#xff0c;找了几篇博客下文算是比较全的。Step1 :登录到自己的Github&#xff0c;查看代码仓库点击“Repositories”进入自己的代码仓库页面…

holdpwd php,PHPMyWind后台管理界面的SQL注入漏洞 - 网站安全

后台管理界面因为过滤不严格导致SQL注入漏洞&#xff0c;可以使权限较低的管理员取得较高权限&#xff0c;以及获取并修改超级管理员的用户名密码。存在问题的代码&#xff0c;admin_save.php 59-101行&#xff0c;SQL语句中的$id存在注入else if($action update){//创始人账号…

网站地图在线生成html,如何制作网站地图(sitemap.html和sitemap.xml)?

总所周知&#xff0c;一个网站的网站地图非常重要&#xff0c;也是SEO站内优化的其中一个基本步骤&#xff0c;可总有人跟我咨询&#xff0c;到底如何制作网站地图&#xff0c;其实很简单&#xff0c;网站地图分为&#xff1a;sitemap.html(百度搜索引擎)和sitemap.xml(谷歌搜索…