带你玩转区块链--实现Dapp众筹项目-第二章-第二节【以太坊篇】

一、意义:

          在上一节课中,我们已经能基于以太坊智能合约做一些简单的Dapp应用了,但在真正生成环境中,这些知识远远不够。为了更好的掌握这部分知识,在这一节中,我们通过一个新项目(众筹)来加强和学习新的知识点。废话不多说,我们言归正传。

项目简介:       

       众筹是指用团购+预购的形式,向网友募集项目资金的模式。众筹利用互联网和SNS传播的特性,让小企业、艺术家或个人对公众展示他们的创意,争取大家的关注和支持,进而获得所需要的资金援助。在传统众筹项目中,资金难以监管、追溯。参与方是拥有很大风险,因为项目方可以融资后拿钱跑路。通过前几节课的学习,我们知道区块链就是一个超级数据库,不可修改,可追溯。所以我们考虑在技术上通过区块链改进众筹项目。

---------------------------------------------------------------------------------------------------------------------------------------

如果您改进了代码或者想对照代码学习,请访问我的Github。

如果您有问题想要讨论。请加我微信:laughing_jk(加我,请备注来源,谢谢)

众筹项目源码:https://github.com/lsy-zhaoshuaiji/FundingFactory

---------------------------------------------------------------------------------------------------------------------------------------

二、需求分析:

1.每个用户都能参与项目和发布项目

2.众筹总金额等于基本筹金乘以份数,每份众筹金一样。

3.项目方可设置一个时间段和份数值,若在某个时间段众筹份数小于某个值,则代表众筹失败,进行合约退款。

4.项目方提出一个花费请求,每个人都有投票的权利,投票后结果不可更改,不可逆。

5.项目方提出一个花费请求,1/2参与人投票赞成票,则代表通过协议。若少于1/2,则不可花费该资金。

6.花费请求通过后,可以手动向某个特点地址进行付款。

三、概要设计

由于该项目中,弱化了项目方的权利,且产生的数据具有可追溯性,不可修改性,所以我们需要在项目中引入以太坊智能合约。该项目采用(B/S架构),具体设计如下:

1.底层使用ETH智能合约实现该项目的数据存储、 (数据库模块)

2.使用React实现前端与用户的交互                        ( 前端模块)

3.使用node.js/web3实现对智能合约的控制和交互 ( 后台模块)

四、详细设计

由于我们设计的是众筹平台,在平台中存在多个众筹项目,一个项目对应着一个合约。所以平台中可能存在多个智能合约。我们把控制多个智能合约的总合约叫做工厂合约(FundingFactorty)。把控制本身项目的合约叫做单例合约(Funding)。

4.1合约编写

4.1.1、思维导图如下图:

 

带你玩转区块链--实现Dapp众筹项目-第二章-第二节【以太坊篇】_第1张图片

4.1.2、单例模式能合约代码

pragma solidity ^0.4.24;
import './fundingFactory.sol';

contract Findding{
    address public manager;
    string public projectName;
    uint256 public targetMoney;
    uint256 public supportMoney;
    uint256 public endTime;
    address [] public Investors;
    SupportFundingContract supportFundings;
    enum RequestStatus{
        Voting,Approving,Completed
    }  
    struct Request{
        string purpose;
        uint256 cost;
        address seller;
        uint256 approvedCount;
        RequestStatus status;
        mapping(address => bool) isVoteMap;
        
    }
    Request [] public allRequests;
    mapping(address=>bool) public isInverstorMap;
    constructor(string _projectName,uint256 _targetMoney,uint256 _supportMoney,uint256 _duration,address _creator,SupportFundingContract _supportFundings) public{
        manager=_creator;
        projectName=_projectName;
        targetMoney=_targetMoney;
        supportMoney=_supportMoney;
        endTime=block.timestamp+_duration;
        supportFundings=_supportFundings;
    }
    //"大黄蜂",10,5,3600
    //"sfsdfsd",5,"0xca35b7d915458ef540ade6068dfe2f44e8fa733c"
    
    modifier OnlyManager(){
        require(msg.sender==manager);
        _;
    }
    function getLastTime()public view returns(uint256){
        return endTime-block.timestamp;
    }
    function InverstorCount()public view returns(uint256){
        return Investors.length;
    }
    function getRequestCount()view public returns(uint256){
        return allRequests.length;
    }
    function Invers() payable public {
        require(supportMoney*10**18==msg.value);
        isInverstorMap[msg.sender]=true;
        Investors.push(msg.sender);
        supportFundings.setFunding(msg.sender,this);
    }
    function Refund() OnlyManager public{
        for (uint256 i=0;i= req.cost*10**18);
        require(req.approvedCount *2 >=Investors.length);
        req.seller.transfer(req.cost*10**18);
        req.status=RequestStatus.Completed;
    }
}

4.1.3、工厂模式智能合约代码

pragma solidity ^0.4.24;
import './Test.sol';


contract FundingFactory{
    address public superManager;
    address [] allFundings;
    mapping(address=>address[]) public createFundings;
    // mapping(address=>address[]) public joinFundings;
    SupportFundingContract supportFundings= new SupportFundingContract();
    
    constructor()public{
        superManager=msg.sender;
    }
    
    function createFundingFactory(string _projectName,uint256 _targetMoney,uint256 _supportMoney,uint256 _duration) public{
        address funding=new Findding(_projectName, _targetMoney, _supportMoney, _duration,msg.sender,supportFundings);
        allFundings.push(funding);
        createFundings[msg.sender].push(funding);
    }
    
    function getAllFundings()public view returns(address []){
        return allFundings;
    }
    function getCreateFundings()public view returns(address[]){
        return createFundings[msg.sender];
    }
    function getSupportFundings()view public returns(address []){
        return supportFundings.getFunding(msg.sender);
    }
}

contract SupportFundingContract{
    mapping(address => address[]) joinFundings;
    function setFunding(address _support,address _funding) public {
        joinFundings[_support].push(_funding);
    }
    function getFunding(address _support)public view returns(address []){
        return joinFundings[_support];
    }
}

4.1.4、全局交互合约

在众筹统计与自己相关的合约时,单例合约需要调用工厂合约的参与者 mapping(address => address [])字段,而在solidity中无法传递复杂参数。由于合约的本质就是地址,地址在solidity中是可以传递的,所以这时候我们需要在创建一个合约,来专门复杂添加/获取与自己相关的合约。

contract SupportFundingContract{
    mapping(address => address[]) joinFundings;
    function setFunding(address _support,address _funding) public {
        joinFundings[_support].push(_funding);
    }
    function getFunding(address _support)public view returns(address []){
        return joinFundings[_support];
    }
}

4.2创建Dapp项目部署合约

4.2.1、准备工作

create-react-app dapp
cd lottery-react
npm install [email protected]
npm install web3
npm install [email protected]
npm i semantic-ui-react
npm i semantic-ui-css
 
//如果报错,就不要担忧,只要package.json里面有上述模块,且能使用就行

在安装后所需依赖后,

1.清理react工程,删除src中除App.js/index.js以外的所有内容,并在src中创建display、eth、utils目录

2.在display中创建ui.js 来编写该项目的前端组件;

3.eth文件夹中创建FundingFactory.js与合约进行交互,

4.在utils创建initWeb3.js,获取用户metamask中传来的web3对象。

4.2.2、创建01-compile.js、02-deploy.js

上节课程中,我们经常创建这两个文件。我就多不解释了。这两个文件是用来编译、部署合约的。但在此项目中由于出现了两个合约,在node.js编译中 会出现引用报错。所以这里建议兄弟们,将两个合约写在一个合约中,或者直接用remix部署到rosten测试网中后,用web3获取合约对象。这里,为了方法调试代码我们建议兄弟们先将两个合约写在一个合约中用node.js编译,在编码过程结束后再直接部署到rosten测试网中。

//01-compile.js
let fs=require('fs');
let solc=require('solc');

let data=fs.readFileSync('./contracts/FundingFactory.sol','utf-8');

let output=solc.compile(data,1);


module.exports = output['contracts'][':FundingFactory'];


//deploy.js
let {interface,bytecode}=require('./01-compile');

let Web3=require('web3');
let web3= new Web3();

web3.setProvider('http://127.0.0.1:8545');

let contract=new web3.eth.Contract(JSON.parse(interface));
let deploy=async ()=>{
    try {
        let accounts = await web3.eth.getAccounts();
        console.log('accounts :', accounts);
        let instance = await contract.deploy({
            data: bytecode,
        }).send({
            from: accounts[0],
            gas: '5000000',
        });
        console.log(instance.options.address);
        module.exports = instance
    } catch (e) {
        console.log(e)
    }
};

deploy();

4.2.3、编写initWeb3.js/获取用户端web3

老版本的metamask(小狐狸)不需要授权、只需要用web3.eth.currentProvider

即可获取web3对象。新版本中的metamask需要先授权,才能获取web3对象、我们通过window.ethereum先判断是否为新版本

let Web3=require('web3');
let web3=new Web3();
let web3Provider;
if (window.ethereum) {
    web3Provider = window.ethereum;
    try {
        // 请求用户授权
        window.ethereum.enable().then()
    } catch (error) {
        // 用户不授权时
        console.error("User denied account access")
    }
} else if (window.web3) {   // 老版 MetaMask Legacy dapp browsers...
    web3Provider = window.web3.currentProvider;
}
web3.setProvider(web3Provider);//web3js就是你需要的web3实例

web3.eth.getAccounts(function (error, result) {
    if (!error)
        console.log(result,"")//授权成功后result能正常获取到账号了
});
module.exports =web3;

4.2.4、编写FunningFactory.js/获取合约实例

let web3= require('../utils/initWeb3');


let abi=[....]//填你自己的ABI
let address='0xE0C2A2f2d0697410D8399c22fff3BAC69F5A5B0E';
let contractInstance=new web3.eth.Contract(abi,address);
console.log(contractInstance.options.address,"oooooooooooooooooooooo");
module.exports=contractInstance;

4.3搭建项目框架、实现合约与前端交互

4.3.1创建createFunningTab组件,实现数据显示

1.修改FundingFactory.js,实现工厂合约与单例合约对象,为了显示单例合约中的详细信息。单例合约返回创建方法

let web3 = require('../utils/initWeb3');
let abi=....
let address='0xe8bf0428371d6ddfeeba85d2451edea443bb8edf';
let FactoryInstance=new web3.eth.Contract(abi,address);
console.log(FactoryInstance.options.address,"oooooooooooooooooooooo");
//单例合约
let FunningABI=....
let newFunningInstance=()=>{
  return new web3.eth.Contract(FunningABI);
};
module.exports={
    FactoryInstance,
    newFunningInstance,
};

  2. 在display文件夹中创建createFunningTab文件夹并创建createFunning.js,并在该文件中获取单例合约,并调用单例合约方法获取单例合约信息。

import React from 'react';
import {Component}from 'react'
import {detailsPromise} from '../../eth/interaction'
import CardList from "../comm/comm";
class CreateFundingTab extends Component{

    state={
        createDetailInfo:[],
    };
    async componentWillMount(){
        let createDetailInfo=await detailsPromise();
        // detailInfo.then(details=>{
        //    console.log(details);
        // });
        console.table(createDetailInfo,"wwwwwwwwwwwwwwwwwww");
        this.setState({
            createDetailInfo,
        })
    }
    render(){
        return(
            
        )
    }
}
export default CreateFundingTab;

在eth文件夹创建interaction.js实现promise封装:

let {FactoryInstance,newFunningInstance}=require('../eth/FundingFactory');
let detailsPromise=async (index)=>{
    let currentFunding=[];
    if (index === 1){
        currentFunding=await FactoryInstance.methods.getAllFundings().call();
    }else if (index === 2){
       currentFunding=await FactoryInstance.methods.getCreateFundings().call();
    }else {
        currentFunding=await FactoryInstance.methods.getSupportFundings().call();
    }
    let details=currentFunding.map(function (v) {
        return new Promise(async (resolve, reject) => {
            try {
                let Funding = newFunningInstance();
                Funding.options.address = v;
                let address=v;
                let manager = await Funding.methods.manager().call();
                let projectName = await Funding.methods.projectName().call();
                let targetMoney = await Funding.methods.targetMoney().call();
                let supportMoney = await Funding.methods.supportMoney().call();
                let endTime = await Funding.methods.endTime().call();
                let balance=await Funding.methods.getBalance().call();
                let InverstorCount=await Funding.methods.InverstorCount().call();
                let detail = {balance,InverstorCount,address,manager, projectName, targetMoney, supportMoney, endTime};
                resolve(detail)
            } catch (e) {
                reject(e)
            }
        });
    });
    let detailInfo=Promise.all(details);
    return detailInfo
};

export {
    detailsPromise,
}

在src/display/comm文件夹中的comm.js,进行前端渲染:

import React from 'react'
import { Card,List, Image,Progress } from 'semantic-ui-react'
const imageSrc = 'img/logo.png';

const CardList = (props) => {
   let details=props.detailInfo;
   let cards=details.map(detail=>{
       return 
   });

  return (
      
          {
              cards
          }
      
  )
};
const CardExample = (props) => {
    let detail2=props.detail1;
    let {balance,InverstorCount,address,manager, projectName, targetMoney, supportMoney, endTime}=detail2;
    let percent=parseFloat(balance)/parseFloat(targetMoney)*100;
    return (
        
            
            
                {projectName}
                
                    剩余时间:{endTime}
                    
                
                
                    众筹目标:{targetMoney} Wei
                
            
            
                
                    
                        
                            已筹
                            {balance} wei
                        
                    
                    
                        
                            已达
                            {percent}%
                        
                    
                    
                        
                            参与人数
                            {InverstorCount}
                        
                    
                
            

        
    )
};

export default CardList

4.3.2创建allFunningTab组件

        在display文件夹中创建allFunningTab文件夹并创建allFunning.js(代码实现与上相同 ,只需将interactoin.js的index改为1即可)

4.3.3创建supportFunningTab组件

        在display文件夹中创建supportFunningTab文件夹并创建supportFunning.js(代码实现与上相同 ,只需将interactoin.js的index改为3即可)

4.3.3实现发起众筹功能

创建createFundingForm.js

import React, {Component} from 'react';
import {Dimmer, Form, Label, Loader, Segment} from 'semantic-ui-react'
import {createFunding} from "../../eth/interaction";

class CreateFundingForm extends Component {
    state = {
        active: false,
        projectName: '',
        supportMoney: '',
        targetMoney: '',
        duration: '',
    }

    //表单数据数据变化时触发
    handleChange = (e, {name, value}) => this.setState({[name]: value})
    handleCreate = async () => {
        let {active, projectName, targetMoney, supportMoney, duration} = this.state
        console.log('projectName:', projectName)
        console.log('targetMoney:', supportMoney)
        this.setState({active: true})

        try {
            let res = await createFunding(projectName, targetMoney, supportMoney, duration)
            alert(`创建合约成功!\n`)
            this.setState({active: false})

        } catch (e) {

            this.setState({active: false})
            console.log(e)
        }
    }

    render() {
        let {active, projectName, targetMoney, supportMoney, duration} = this.state

        return (
            
Loading
) } } export default CreateFundingForm

修改interaction.js,与合约进行交互

let web3 = require('../utils/initWeb3');
let {FactoryInstance,newFunningInstance}=require('../eth/FundingFactory');
let detailsPromise=async (index)=>{
    let currentFunding=[];
    if (index === 1){
        currentFunding=await FactoryInstance.methods.getAllFundings().call();
    }else if (index === 2){
       currentFunding=await FactoryInstance.methods.getCreateFundings().call();
    }else {
        currentFunding=await FactoryInstance.methods.getSupportFundings().call();
    }
    let details=currentFunding.map(function (v) {
        return new Promise(async (resolve, reject) => {
            try {
                let Funding = newFunningInstance();
                Funding.options.address = v;
                let address=v;
                let manager = await Funding.methods.manager().call();
                let projectName = await Funding.methods.projectName().call();
                let targetMoney = await Funding.methods.targetMoney().call();
                let supportMoney = await Funding.methods.supportMoney().call();
                let endTime = await Funding.methods.endTime().call();
                let balance=await Funding.methods.getBalance().call();
                let InverstorCount=await Funding.methods.InverstorCount().call();
                let detail = {balance,InverstorCount,address,manager, projectName, targetMoney, supportMoney, endTime};
                resolve(detail)
            } catch (e) {
                reject(e)
            }
        });
    });
    let detailInfo=Promise.all(details);
    return detailInfo
};
let createFunding=(projectName, targetMoney, supportMoney, duration)=>{
  return new Promise(async (resolve, reject) => {
      // function createFundingFactory(string _projectName,uint256 _targetMoney,uint256 _supportMoney,uint256 _duration) public{
      try {
          let accounts = await web3.eth.getAccounts();
          let instance = await FactoryInstance.methods.createFundingFactory(projectName, targetMoney, supportMoney, duration).send({
              from: accounts[0],
          });
          resolve(instance)
      } catch (e) {
          reject(e)
      }
  })
};
export {
    detailsPromise,
    createFunding,
}

4.3.3实现参与众筹功能

修改allfundingTab

import React from 'react';
import {Component}from 'react'
import {detailsPromise,handleInvestFunc} from '../../eth/interaction'
import CardList from "../comm/comm";
import {Dimmer, Form, Label, Loader, Segment} from 'semantic-ui-react'
class AllFundingTab extends Component{

    state={
        active: false,
        allFundingTab:[],
        seletedFundingDetail: ''
    };
    async componentWillMount(){
        let allFundingTab=await detailsPromise(1);
        console.table(allFundingTab,"wwwwwwwwwwwwwwwwwww");
        this.setState({
            allFundingTab,
        })
    }
    onCardClick = (seletedFundingDetail) => {
        console.log("aaa :", seletedFundingDetail);
        this.setState({
            seletedFundingDetail
        })
    };
    handleInvest = async () => {
        let {balance,InverstorCount,address,manager, projectName, targetMoney, supportMoney, endTime} = this.state.seletedFundingDetail;
        //需要传递选中合约地址
        //创建合约实例,参与众筹(send, 别忘了value转钱)
        this.setState({active: true})

        try {
            let res = await handleInvestFunc(address, supportMoney)
            this.setState({active: false})
            console.log('1111111')

        } catch (e) {

            this.setState({active: false})
            console.log(e)
        }
    }

    render(){
        let {balance,InverstorCount,address,manager, projectName, targetMoney, supportMoney, endTime} = this.state.seletedFundingDetail;
        return(
            

参与众筹

支持中
) } } export default AllFundingTab;

修改comm.js

import React from 'react'
import { Card,List, Image,Progress } from 'semantic-ui-react'
const imageSrc = 'img/logo.png';

const CardList = (props) => {
   let details=props.detailInfo;
   let onCardClick=props.onCardClick
   let cards=details.map(detail=>{
       return 
   });

  return (
      
          {
              cards
          }
      
  )
};

const CardExample = (props) => {
    let detail2=props.detail1;
    let {balance,InverstorCount,address,manager, projectName, targetMoney, supportMoney, endTime}=detail2;
    let percent=parseFloat(balance)/parseFloat(targetMoney)*100;
    return (
        props.onCardClick(detail2)}>
            
            
                {projectName}
                
                    剩余时间:{endTime}
                    
                
                
                    众筹目标:{targetMoney} Wei
                
            
            
                
                    
                        
                            已筹
                            {balance} wei
                        
                    
                    
                        
                            已达
                            {percent}%
                        
                    
                    
                        
                            参与人数
                            {InverstorCount}
                        
                    
                
            

        
    )
};

export default CardList

修改interaction.js

let web3 = require('../utils/initWeb3');
let {FactoryInstance,newFunningInstance}=require('../eth/FundingFactory');
let detailsPromise=async (index)=>{
    let currentFunding=[];
    if (index === 1){
        currentFunding=await FactoryInstance.methods.getAllFundings().call();
    }else if (index === 2){
        console.log("uuuuuuuuuuuuuuuuuuuuuuuuuu")
        currentFunding=await FactoryInstance.methods.getCreateFundings().call();
        console.log("currentFunding:",currentFunding)
    }else if (index === 3){
        currentFunding=await FactoryInstance.methods.getSupportFundings().call();
    }
    let details=currentFunding.map(function (v) {
        return new Promise(async (resolve, reject) => {
            try {
                let Funding = newFunningInstance();
                Funding.options.address = v;
                let address=v;
                let manager = await Funding.methods.manager().call();
                let projectName = await Funding.methods.projectName().call();
                let targetMoney = await Funding.methods.targetMoney().call();
                let supportMoney = await Funding.methods.supportMoney().call();
                let endTime = await Funding.methods.endTime().call();
                let balance=await Funding.methods.getBalance().call();
                let InverstorCount=await Funding.methods.InverstorCount().call();
                let detail = {balance,InverstorCount,address,manager, projectName, targetMoney, supportMoney, endTime};
                resolve(detail)
            } catch (e) {
                reject(e)
            }
        });
    });
    let detailInfo=Promise.all(details);
    return detailInfo
};
let createFunding=(projectName, targetMoney, supportMoney, duration)=>{
  return new Promise(async (resolve, reject) => {
      // function createFundingFactory(string _projectName,uint256 _targetMoney,uint256 _supportMoney,uint256 _duration) public{
      try {
          let accounts = await web3.eth.getAccounts();
          let instance = await FactoryInstance.methods.createFundingFactory(projectName, targetMoney, supportMoney, duration).send({
              from: accounts[0],
          });
          resolve(instance)
      } catch (e) {
          reject(e)
      }
  })
};
let handleInvestFunc = (address, supportMoney) => {
    return new Promise(async (resolve, reject) => {
        try { //创建合约实例
            let fundingInstance = newFunningInstance()
            //填充地址
            fundingInstance.options.address = address

            let accounts = await web3.eth.getAccounts()

            let res = await fundingInstance.methods.Invers().send({
                    from: accounts[0],
                    value: supportMoney,
                }
            )
            resolve(res)
        } catch (e) {
            reject(e)
        }
    })
}
export {
    detailsPromise,
    createFunding,
    handleInvestFunc,
}

在solidity中使用了msg.sender就要使用from字段,不然会显示报错

4.3.4实现发起项目和项目投票

由于该功能依然是是react和合约的交互,所以在这里就不放代码了,如果有需要代码的童鞋请去github上下载,

实现逻辑:1.在前端创建表单,2.通过表单传递函数到interaction中,3.在interaction中实现与合约的交互

五、项目总结

            该项目主要目的是实现工厂模式和单例模式的交互,在此部分中,我们需要谨记工厂模式的合约是可以直接获取的,而单例合约的address需要传递后调用。其余的便是react的知识,通过该项目我们更加熟悉了合约部分,接下来我们继续研究ETH的其他功能

 

你可能感兴趣的:(区块链开发,golang知识,挖矿教程)