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

news/2024/5/14 15:35:58/文章来源:https://blog.csdn.net/weixin_34082789/article/details/85081929
修订日期姓名邮箱
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

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

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

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

相关文章

网站开发需要哪些技术_自己建网站要掌握哪些技术-广州福星网站建设

自助建站最大的好处&#xff0c;可以不用找人或花高价找建站公司来建站&#xff0c;但自己建站却存在一定技术门槛&#xff0c;而且这个技术门槛非常的高&#xff0c;主要因为建站并不是说&#xff0c;通过一门技术就可以搞定&#xff0c;做网站所需要技术分为三大块&#xff0…

电商网站模板_4个步骤,教你这样制作跨境电商网站

随着全球互联网经济的发展&#xff0c;外贸从业者也需要好好利用互联网来开展业务了。而制作一个自己的跨境电商网站&#xff0c;就是比较好的营销卖货方式。不过大部分人都不知道该如何制作&#xff0c;所以接下来就跟大家科普下跨境电商网站制作教程&#xff1a;Step1&#x…

制作服务器配置,制作视频网站怎么配置服务器

一个视频网站&#xff0c;它所需的流量远远大于普通网站。它对于服务器的要求也是更高。对于制作视频网站&#xff0c;服务器硬件一个最好是8核 cpu &#xff0c; 最低16G内存建议32G内存&#xff0c;两个以上的100M网卡&#xff0c;系统可以选择windows也可以选择linux。假如你…

类似tom网站给系统换肤的实现方法。

首先&#xff0c;换肤设计要保证皮肤更改的信息能够存在cookie里&#xff0c;使得用户下次访问的时候还能够看到自己上次的设置。这里换肤我们用到的是通过js调用不同的样式。然而在我们正在做的系统中&#xff0c;我们用到了IFRAME&#xff0c;要保证整体页面的一致&#xff0…

MySQL就事器知名网站中的运用量

2008 年4月18日&#xff0c;在Alexa安插的一次“Scaling MySQL -- Up or Out?” 的小组申辩中&#xff0c;MySQL、Sun、Flickr、Fotolog、Wkipedia、Facebook、YouTube等国际知名网站的DBA们&#xff0c;对其网站MySQL数据库就事器、Web就事器、缓存就事器的数目&#xff0c;M…

网站安全漏洞--大全

原文网址&#xff1a;网站安全漏洞--大全_IT利刃出鞘的博客-CSDN博客 简介 本文介绍网站常见的一些安全漏洞&#xff0c;包括&#xff1a;SQL 注入、越权操作、跨站脚本攻击(XSS)、跨站请求伪造(CSRF)、DDoS 攻击、JSON 劫持、暴力破解、HTTP 报头追踪漏洞、信息泄露、文件上传…

[存底]加速,加速,再加速:来自Google的网站加速技巧大全

原文&#xff1a;http://developer.51cto.com/art/200906/132210.htm 困扰许多网站所有者很久的一个问题是网站访问速度总是那么慢。想购买独立带宽&#xff0c;预算不允许&#xff0c;想购买CDN加速&#xff0c;价格又太贵。那有没有经济实惠的解决办法呢&#xff1f;从目前的…

python人工智能_Python爬虫学习教程 bilibili网站视频爬取!【附源码】

Python爬虫学习教程&#xff0c;万物皆可爬&#xff01;每个技术大牛都是从基础慢慢的提升上去的&#xff0c;基础知识越深以后的发展越牛&#xff01;学好python&#xff0c;才能玩转python&#xff0c;那到底怎么才能学好python&#xff1f;通过爬取b站案例带领你快速掌握爬虫…

ASP.NET : 如何为网站添加自定义的文件类型

我们知道&#xff0c;在ASP.NET网站中&#xff0c;默认页面的扩展名是aspx.要想实现在网站中提供特殊的文件类型&#xff0c;例如Test之类的,就可以考虑通过HttpHandler来实现。下面是一个小的例子 1. HttpHandlerusing System; using System.Collections.Generic; using Syste…

aspnetcore upload form提交_第八:网站网址提交给搜索引擎

第八&#xff1a;网站网址提交给搜索引擎网站更新了文章&#xff0c;如果你想搜索引擎快速收录网页&#xff01;把网址提交给搜索引擎&#xff0c;是一种辅助网页收录的技巧。会围绕着搜索引擎提交这个话题&#xff0c;一一的介绍网址提交的方法。★ 网址提交方式以百度为例&am…

网站用户登录验证:Servlet+JSP VS Struts书剑恩仇录

? 什么是Struts框架 –从不同的角度看待Struts框架 –Struts框架的优点 ? 下载安装Struts框架 –下载配置Struts框架 –测试Struts框架 –安装Struts应用程序 –访问Struts文档 ? Struts 中常用组件 –ActionServlet –Action –ActionForm –ActionMapping –ActionForward…

网站防御DDoS的方案--高防节点(高防IP)

原文网址&#xff1a;网站防御DDoS的方案--高防节点(高防IP)_IT利刃出鞘的博客-CSDN博客 简介 本文介绍如何使用高防节点防御DDoS攻击。 本文包含的内容有&#xff1a;DDos的概念&#xff0c;高防节点的防御原理&#xff0c;提供高防节点的一些厂家&#xff0c;高防节点的配置…

linux 部署asp网站,Linux Ubuntu下部署Asp.net Core Web

Linux Ubuntu下部署Asp.net Core WebLinux Ubuntu下部署Asp.net Core Web使用的是Ubuntu14.04版本&#xff0c;.Net Core 版本2.1打开终端并运行以下命令&#xff1a;wget -q https://packages.microsoft.com/config/ubuntu/19.04/packages-microsoft-prod.deb -O packages-mic…

UX Pin - 一款在线界面设计网站

UXPorn首页上以Pinterest的风格聚合了很多优秀的UI设计&#xff0c;除了供大家参考学习外&#xff0c;也可以在UXPIN App中直接使用。 进入UXPIN App, 可以拖动各种元素到canvas上&#xff0c;添加用户交互事件&#xff0c;并支持自定义component, version control, team colla…

方配网站服务器 32位,方配网站服务器

方配网站服务器 内容精选换一换当您想在Internet上通过域名访问您的网站时&#xff0c;可以通过本操作将域名托管至华为云的云解析服务&#xff0c;并为域名添加解析记录。例如&#xff0c;搭建一个网站服务器&#xff0c;采用IPv4格式的弹性IP地址。如果想要实现通过域名“exa…

php网站怎么把上传的图片加载在头像上,JavaScript

js实现图片上传并正常显示项目经常会用到头像上传&#xff0c;那么怎么实现呢&#xff1f;首先是HTML布局&#xff1a;缩略图jquery方式&#xff0c;IE不支持&#xff0c;但IE会获得绝对的上传路径信息&#xff1a;function getObjectURL(file) {var url null ;if (window.cre…

b2c 多语言,3个可以让B2C外贸网站更成功的设计

让B2C外贸网站更具备竞争力&#xff0c;也可以从网站上的细节入手。人类是一种很复杂的动物&#xff0c;一些小设计往往能够直达内心&#xff0c;引导他们做出购买行为。下面5个小设计希望能够引起路过的电商朋友一些启发。行为召唤Call To Action充斥在各种购物环境中&#xf…

php手机网站视频上传播放器,TP中PC与手机端视频播放功能二次开发

原标题&#xff1a;TP中PC与手机端视频播放功能二次开发在使用ThinkPHP进行长沙网站开发中&#xff0c;发现新闻中的视频文件都不能正常播放。在DWPCMS开发的项目中存在同样问题&#xff0c;但PC端能正常播放。DWPCMS和TP的视频播放都是使用flash播放器&#xff0c;但Android(安…

360抢票 网站维护中 你的登录被踢了!

本来在超市犹豫到底该买哪种暖手袋&#xff0c;犹豫了差不多半个多小时&#xff0c;还没决定好&#xff0c;一看时间还有8分钟到10点&#xff0c;遂狂奔回寝室抢票。 结果&#xff0c;360抢票被12306秒了—— 猜测原因是12306的验证码改了&#xff08;变成动态的了……&#xf…

win2008服务器运行Linux,Windows Server 2008 R2 下配置证书服务器和HTTPS方式访问网站...

前言App Store上的所有App应用以后将鼓励或者强制开启ATS功能。苹果的ATS(App Transport Security)对服务器硬性3点要求&#xff1a;① ATS要求TLS1.2或者更高&#xff0c;TLS 是 SSL 新的别称。② 通讯中的加密套件配置要求支持列出的正向保密。③ 数字证书必须使用sha256或者…