首页 > 其他分享 >简单Dapp的开发

简单Dapp的开发

时间:2024-01-20 22:55:44浏览次数:29  
标签:string res Dapp 开发 简单 合约 internalType type name

简单Dapp的开发

实验概述

DApp(Decentralized Application)去中心化应用,自 P2P 网络出现以来就已经存在,是一种运行在计算机 P2P 网络而不是单个计算机上的应用程序。
DApp 以一种不受任何单个实体控制的方式存在于互联网中。在区块链技术产生之前,BitTorrent,Popcorn Time,BitMessage等都是运行在P2P网络上的DApp,随着区块链技术的产生和发展,DApp 有了全新的载体和更广阔的发展前景。
DApp 应具备代码开源、激励机制、非中心化共识和无单点故障四个要素,而最为基本的 DApp 结构即为前端+智能合约形式。
本实验以以太坊为基础,首先用 Solidity 语言编写实现会议报名登记功能的智能合约,加深编写智能合约的能力;之后学习以太坊私有链的搭建、以及合约在私有链上的部署,从而脱离 Remix,学习更为强大的 Truffle 开发组件;进而学习 web3.js 库,实现前端对智能合约的调用,构建完整的 DApp;最后可对该DApp 加入个性化机制,例如加入 Token 机制等,作为实验选做项。该实验实现了一个简单的 DApp,但包含了 DApp 开发的必备流程,为将来在以太坊上进行应用开发打下了基础。
实验内容概述如下:
A. 编写实现会议报名功能的智能合约(发起会议,注册,报名会议,委托报名,事件触发)
B. 利用 Truffle 套件将合约部署到以太坊私有链(私有链搭建,合约部署,合约测试)
C. 利用 web3.js 实现前端对合约的调用(账户绑定、合约 ABI、RPC 调用)

实验 6-1 会议报名登记系统的基本功能与实现

完成合约文件Enrollment.sol的编写,实现一个报名系统,系统应具有以下功能:

合约参与方包含一个管理员以及其余参与者,管理员可以发起不止一个会议,并指定会议信息以及总人数。参与者首先需要进行注册,将个人基本信息与以太坊地址相关联,并存储在合约上,之后可进行报名,或委托他人为自己报名。当会议报名人满时,该会议将不再可报名。当合约内某些数据发生变化时,应能够触发事件(event)使前端重新获取并渲染数据,例如当某个会议报名人满时,应触发相应事件使前端及时更新可报名会议列表。

实现委托函数及为受托者报名函数
function delegate(address addr) public{
	trustees[addr].push(participants[msg.sender]);
}
function enrollFor(string memory username,string memory title) public returns(string memory){
	uint index = 0;
	for (uint i = 0; i < trustees[msg.sender].length; i++) {
		if (keccak256(bytes(trustees[msg.sender][i].name)) == keccak256(bytes(username))) {
			index = i;
			break;
		}
	}
	for (uint i = 0; i < conferences.length; i++){
		if (keccak256(bytes(conferences[i].title)) == keccak256(bytes(title))){
			require(conferences[i].current<conferences[i].max,"Enrolled full");
			conferences[i].current += 1;
			if(conferences[i].current==conferences[i].max){
				emit ConferenceExpire(title);
			}
			trustees[msg.sender][index].confs.push(title);
		}
	}
	uint len = trustees[msg.sender][index].confs.length;
	require(len>0,"Conference does not exist");
	return trustees[msg.sender][index].confs[len-1];
}
练习6-1

1)应在合约的哪个函数指定管理员身份?如何指定?

答:在合约的构造函数中制定管理员的身份。通常将创建会议,也就是部署合约的人设置为管理员,所以在此处指定msg.sender设置为管理员。

2)在发起新会议时,如何确定发起者是否为管理员?简述 require()、assert()、revert()的区别。

答:在上述合约中,通过语句require(msg.sender==admin,"permission denied")来检查发起者是否为管理员。在该语句中,使用require()来检查当前交易的发起者是否为合约的管理员,如果不是,则出发一个异常,终止函数执行,并返回错误消息"permission denied"。

require()、assert()、revert()的区别:当语句执行失败时,require()和revert()会返还相应的gas,而assert()则不会返还相应的gas。

3)简述合约中用 memory 和 storage 声明变量的区别。

答:memory:memory 用于在函数内部临时存储数据;变量在 memory 中声明时,它们的值只在函数的执行期间存储,函数执行完毕后,memory 中的数据将被清除;通常用于存储函数调用中的参数或局部变量,临时操作数据。

storage:storage 用于永久性存储数据,通常用于状态变量;变量在 storage 中声明时,其值将永久保存在以太坊的区块链上,并且可以在不同的交易和函数调用之间保持一致;通常用于存储合约的状态,如合约的状态变量或持久化数据。

实验6-2 学习用 Truffle 组件部署和测试合约

利用 truffle 组件对实验 6-1 的合约进行测试并部署到本地私链上。利用 truffle 初始化一个以太坊项目,将合约Enrollment.sol 放入 contracts 文件夹,并编写部署名为 2_deploy_contracts.js 的脚本。然后为合约编写测试文件。利用 Ganache 搭建私链后,在终端进入 truffle 项目 lab8 的目录,输入 truffle test 进行测试。测试完成后在终端输入 truffle migrate 进行合约编译和部署。

6-2.2 新建 truffle 项目并导入合约

初始化一个以太坊项目,并导入相关合约文件。

2_deploy_contracts.js

const Enrollment = artifacts.require("Enrollment");

module.exports = function(deployer) {
  deployer.deploy(Enrollment);
};

truffle-config.js

module.exports = {
  networks: {
    development: {
     host: "127.0.0.1",     // Localhost (default: none)
     port: 7545,            // Standard Ethereum port (default: none)
     network_id: "*",       // Any network (default: none)
    },
  },
};

6-2.3 为合约编写测试文件

TestEnrollment.sol

    function testEnroll() public{
        Enrollment test=new Enrollment();
        string memory expected="conf1";
        test.newConference("conf1","beijing",30);
        Assert.equal(test.enroll("conf1"),expected,"Enroll failed");
    }
6-2.4 Ganache 搭建私链

打开 Ganache 客户端,选择 NEW WORKSPACE,在 ADD PROJECT 处导入上一步配置的 truffle-config.js 文件。此时将会在本地运行一个以太坊私链,并创建 10 个账户供使用。

image

6-2.5 对合约进行测试和部署

在终端进入 truffle 项目 lab8 的目录,输入“truffle test”,输出结果如下:

image

使用 Ganache重新搭建一条私链,然后在终端输入“truffle migrate”完成合约的编译和部署。控制台会输出各合约的部署情况以及总共的 gas 花费,Ganache 中的第一个账户即为该合约的部署者。

image

image

image

练习 6-2:观察合约的部署过程

观察部署完成后 Ganache 的 Blocks、Transactions 以及 Logs 记录,记录如下:

image

image

image

详细查看每条交易的信息:

image

image

image

image

练习 6-2:观察合约的部署过程

答:合约的部署流程:首先对链进行的初始化,然后进行合约的编写,进行详细的合约规定。接下来对合约进行编译,将其转换为字节码。然后选择区块链平台,创建钱包和账户。最后进行合约的部署,将编译后的合约部署到网络。当区块链网络确认合同已被添加到区块链中时,交易成功。

合约的调用流程:首先创建交易,该交易包括调用智能合约的数据。接下来对交易进行签名并将交易广播到区块链网络。接下来等待交易被确认,一但确认,合约中的操作将被执行。

实验 6-3 利用 Web3.js 实现合约与前端的结合

通过Web3.js实现智能合约和前端之间的调用和订阅:通过前端对以太坊节点进行 RPC 调用,执行合约中的函数,并将合约返回的数据,以及订阅的合约事件提醒及时展示在前端界面。按照实例更改相应组件下的 js 文件以实现对组件的交互,将前端项目中的 src/contracts/contract.js 文件中的 ABI 位置代码更改为合约编译后自动生成的 ABI 信息。

6-3.1 前端界面接口 & 6-3.3 通过 Web3.js 实现前端与合约交互 & 练习 6-3 参考上述注册组件代码中交互代码的写法,完成另外四个表单类组件对合约的调用

对表单中7个文件的index.js代码进行修改和补充,完成对合约的调用,实现合约与前端的结合:

conflist

const mapDispatchToProps = (dispatch) => {
  return {
    submit(title) {
    contract.methods.enroll(title) //输入参数
    .send({from:window.web3.eth.accounts[0]},function(err,res){console.log(res)})  //function中的res为方法返回值
    .then((res)=>console.log(res)); //该res为交易执行完后的具体交易信息,如TxHash等

    dispatch({
        type: 'submit_enroll'
      })
    },

    handleChange(e) {
      dispatch({
        type: 'enroll_title',
        value: e.target.value
      })
    },
  }
}

delegate

const mapDispatchToProps = (dispatch) => {
  return {
    submit(address) {
      //调用合约
      contract.methods.delegate(address) //输入参数
      .send({from:window.web3.eth.accounts[0]},function(err,res){console.log(res)})  //function中的res为方法返回值
      .then((res)=>console.log(res)); //该res为交易执行完后的具体交易信息,如TxHash等

      dispatch({
        type: 'submit_delegate'
      })
    },

    handleChange(e) {
      dispatch({
        type: 'address',
        value: e.target.value
      })
    },
  }
}

enroll

const mapDispatchToProps = (dispatch) => {
  return {
    submit(title) {
    contract.methods.enroll(title) //输入参数
    .send({from:window.web3.eth.accounts[0]},function(err,res){console.log(res)})  //function中的res为方法返回值
    .then((res)=>console.log(res)); //该res为交易执行完后的具体交易信息,如TxHash等

    dispatch({
        type: 'submit_enroll'
      })
    },

    handleChange(e) {
      dispatch({
        type: 'enroll_title',
        value: e.target.value
      })
    },
  }
}

enrollfor

const mapDispatchToProps = (dispatch) => {
  return {
    submit(username,title) {
      contract.methods.enrollFor(username,title) //输入参数
      .send({from:window.web3.eth.accounts[0]},function(err,res){console.log(res)})  //function中的res为方法返回值
      .then((res)=>console.log(res)); //该res为交易执行完后的具体交易信息,如TxHash等
  
      dispatch({
          type: 'submit_enrollfor'
        })
      },

    handleChange(e) {
      if (e.target.placeholder === 'Title of Conference')
        dispatch({
          type: 'enrollfor_title',
          value: e.target.value
        })
      else
        dispatch({
          type: 'enrollfor_username',
          value: e.target.value
        })
    },
  }

myconf

componentDidMount(){
    //先执行一遍查询操作
    contract.methods.queryMyConf()
    .call({from:window.web3.eth.accounts[0]},(err,res)=>{
      //将返回的数组依次压入data中
      this.setState({loading: true});
      if(res != null){
        for(var i=0;i<res.length;i=i+1){
          data.push({title:res[i]});
        }
    }
      else{
        data.push({'title': 'no'});
    }
  })
    .then(()=>{
      //更新状态,使页面数据重新渲染
      this.setState({loading: false});
    });

    contract.events.MyNewConference({
        filter: {}, 
        fromBlock: window.web3.eth.getBlockNumber()
    }, (error, event)=>{
      this.setState({loading: true});
      data.push({conf:event.returnValues[0]});
      this.setState({loading: false});
     })
}

newconf

const mapDispatchToProps = (dispatch) => {
  return {
    submit(title,detail,limitation) {
      contract.methods.newConference(title,detail,limitation) //输入参数
      .send({from:window.web3.eth.accounts[0]},function(err,res){console.log(res)})  //function中的res为方法返回值
      .then((res)=>console.log(res)); //该res为交易执行完后的具体交易信息,如TxHash等

      dispatch({
        type: 'submit_newconf'
      })
    },

    handleChange(e) {
      switch (e.target.placeholder) {
        case 'Title':
          dispatch({
            type: 'newconf_title',
            value: e.target.value
          })
          break;
        case 'Detail':
          dispatch({
            type: 'detail',
            value: e.target.value
          })
          break;
        case 'Limitation':
          dispatch({
            type: 'limitation',
            value: e.target.value
          })
          break;
        default:
          break;
      }
    },
  }
}

signup

const mapDispatchToProps = (dispatch) => {
  return {
    submit(username,extra) {
      contract.methods.signUp(username,extra) //调用合约signUp方法
      .send({from:window.web3.eth.accounts[0]},function(err,res){console.log(res)})  //function中的res为方法返回值
      .then((res)=>console.log(res)); //该res为交易执行完后的具体交易信息,如TxHash等

      dispatch({
        type: 'submit_signup'
      })
    },

    handleChange(e) {
      if (e.target.placeholder === 'username')
        dispatch({
          type: 'username',
          value: e.target.value
        })
      else
        dispatch({
          type: 'extra',
          value: e.target.value
        })
    },
  }
6-3.2 在前端项目文件中配置合约信息

将/build 文件夹下会生成的json文件中的ABI信息拷贝到contract.js文件中;在 Ganache 的 contracts 中找到部署的 Enrollment 合约,将合约地址复制到contract.js中。

contract.js

import Web3 from 'web3';
//在此粘贴ABI信息
const abi = [
  {
    "inputs": [],
    "payable": false,
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "string",
        "name": "title",
        "type": "string"
      }
    ],
    "name": "ConferenceExpire",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "string",
        "name": "title",
        "type": "string"
      }
    ],
    "name": "MyNewConference",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "string",
        "name": "title",
        "type": "string"
      },
      {
        "indexed": false,
        "internalType": "string",
        "name": "detail",
        "type": "string"
      }
    ],
    "name": "NewConference",
    "type": "event"
  },
  {
    "constant": true,
    "inputs": [],
    "name": "admin",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
  },
  {
    "constant": true,
    "inputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "name": "conferences",
    "outputs": [
      {
        "internalType": "string",
        "name": "title",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "detail",
        "type": "string"
      },
      {
        "internalType": "uint256",
        "name": "max",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "current",
        "type": "uint256"
      }
    ],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
  },
  {
    "constant": true,
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "participants",
    "outputs": [
      {
        "internalType": "string",
        "name": "name",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "extra",
        "type": "string"
      }
    ],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
  },
  {
    "constant": false,
    "inputs": [
      {
        "internalType": "string",
        "name": "username",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "extra",
        "type": "string"
      }
    ],
    "name": "signUp",
    "outputs": [
      {
        "internalType": "string",
        "name": "",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "",
        "type": "string"
      }
    ],
    "payable": false,
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "constant": false,
    "inputs": [
      {
        "internalType": "string",
        "name": "title",
        "type": "string"
      }
    ],
    "name": "enroll",
    "outputs": [
      {
        "internalType": "string",
        "name": "",
        "type": "string"
      }
    ],
    "payable": false,
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "constant": false,
    "inputs": [
      {
        "internalType": "string",
        "name": "title",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "detail",
        "type": "string"
      },
      {
        "internalType": "uint256",
        "name": "max",
        "type": "uint256"
      }
    ],
    "name": "newConference",
    "outputs": [
      {
        "internalType": "string",
        "name": "",
        "type": "string"
      }
    ],
    "payable": false,
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "constant": true,
    "inputs": [],
    "name": "queryConfList",
    "outputs": [
      {
        "internalType": "string[]",
        "name": "",
        "type": "string[]"
      }
    ],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
  },
  {
    "constant": true,
    "inputs": [],
    "name": "queryMyConf",
    "outputs": [
      {
        "internalType": "string[]",
        "name": "",
        "type": "string[]"
      }
    ],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
  },
  {
    "constant": false,
    "inputs": [
      {
        "internalType": "address",
        "name": "addr",
        "type": "address"
      }
    ],
    "name": "delegate",
    "outputs": [],
    "payable": false,
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "constant": false,
    "inputs": [
      {
        "internalType": "string",
        "name": "username",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "title",
        "type": "string"
      }
    ],
    "name": "enrollFor",
    "outputs": [
      {
        "internalType": "string",
        "name": "",
        "type": "string"
      }
    ],
    "payable": false,
    "stateMutability": "nonpayable",
    "type": "function"
  }
];

// 在此粘贴合约地址(去掉0x)
const address = 'CFE3031dc731DD36eE88B13d6dbD0794Ba633Eae';

// window.web3.currentProvider为当前浏览器的web3 Provider
const web3 = new Web3(window.web3.currentProvider);

// 允许连接到metamask
window.ethereum.enable();

// 导出合约实例
export default new web3.eth.Contract(abi, address);

部署完成后,在lab8-frontend文件夹中打开文件夹,先执行npm install命令,然后再执行npm start命令。执行之后,成功打开部署的智能合约前端界面,并且能够成功打开MetaMask。

image

在MetaMask中创建一个账户并登录,之后在网页中注册一个用户:

image

可以看到Chrome支付成功的提示:

image

在Ganache中也可以查询到交易记录:image

新建一个会议:

image

支付后在Ganache中查询交易记录:

image

加入刚才创建的会议:

image

支付后在Ganache中也查询该条交易记录:

image

刷新页面后可以查询到会议列表和已加入的会议中包含刚才创建的Block Chain会议。

image

问题:这里的调用,应采用 call()方法还是 send()方法?

对于展示类组件,如可报名会议列表和本人已报名会议,通常应使用 call() 方法来调用合约中的查询类方法。call() 方法用于读取合约的状态,而不会创建交易,因此不需要消耗以太币。上述查询方法仅涉及读取数据,而不涉及修改区块链状态。而 send() 方法用于发送事务,可能涉及到交易费用和修改区块链状态。

标签:string,res,Dapp,开发,简单,合约,internalType,type,name
From: https://www.cnblogs.com/Silverplan/p/17977294

相关文章

  • 软件测试/测试开发/全日制|Page Object模式:为什么它是Web自动化测试的必备工具
    为UI页面写测试用例时(比如web页面,移动端页面),测试用例会存在大量元素和操作细节。当UI变化时,测试用例也要跟着变化,PageObject很好的解决了这个问题。使用UI自动化测试工具时(包括selenium,appium等),如果无统一模式进行规范,随着用例的增多会变得难以维护,而PageObject让自......
  • springboot项目结合filter,jdk代理实现敏感词过滤(简单版)
    我们对getParameter()这个方法得到的参数进行敏感词过滤。实现思路:利用过滤器拦截所有的路径请求同时在在过滤器执行的时候对getParameter得到的value值进行过滤。最后呢,到我们自己的实现的逻辑中呢?这个value值就被我们做过处理了。1:自定义的过滤配置文件把文件位置放在resource下的......
  • 用c语言编写一个简单的学生信息的录入查询
    include<stdio.h>include<string.h>structstudent{charname[20];//使用结构体对姓名年龄分数进行赋值intage;intscore;}st[3]={{"jack",18,80},{"Rose",17,85},{"tom",19,60}};intmain(intargc,charconst*argv[]){c......
  • 云计算-代码开发流水线及CCE容器集群使用案例
    总结自己在使用华为云商业CI/CD代码流水和CCE容器集群部署案例学无止尽啊新项目构建镜像使用华为codearts代码流水线,详细见官方文档https://support.huaweicloud.com/productdesc-devcloud/devcloud_pdtd_00000.html以部署report-service构建测试镜像为例dockerfile文件前端FRO......
  • 瀑布开发和敏捷开发的区别是什么
    一、开发流程不同瀑布开发采用线性的开发流程,按照预先规划的顺序依次进行需求分析、设计、编码、测试和维护等环节。每个环节都有明确的交付物和里程碑。开发团队在完成上一个环节后才能进入下一个环节。敏捷开发采用迭代和增量的开发方式。开发工作被划分为短期的迭代周期,每个......
  • 二进制免安装的方式,部署java1.8开发环境
    (1)配置Java环境#1.下载二进制压缩文件[root@servertools]#wgethttps://download.oracle.com/java/18/latest/jdk-18_linux-x64_bin.tar.gz#2.解压Java二进制文件[root@servertools]#tar-xvfjdk-18_linux-x64_bin.tar.gz#3.编写Java代码[root@server~]#catH......
  • 简单剖析Hashmap
    剖析JavaHashmap源码在Java的集合框架中,HashMap是一颗璀璨的明珠。通过深入挖掘其源码,我们将揭开HashMap的神秘面纱,理解其底层原理、扩容机制和数据结构。1.HashMap源码导读我们首先来看一段简单的代码,创建一个空的HashMap:importjava.util.HashMap;publicclass......
  • P4148 简单题 题解
    QuestionP4148简单题有一个\(n\timesn\)的棋盘,每个格子内有一个整数,初始时全部为\(0\),现在需要维护两种操作1xy将格子\(x,y\)里的数字加上\(A\)2x1y1x2y2输出\(x_1,y_1,x_2,y_2\)这个矩形内的数字和强制在线Solution因为强制在线,没法用CDQ什么乱搞,这......
  • 下一代云原生网关Higress:基于Wasm开发JWT认证插件
    什么是HigressHigress是基于阿里内部的EnvoyGateway实践沉淀、以开源Istio+Envoy为核心构建的下一代云原生网关,实现了流量网关+微服务网关+安全网关三合一的高集成能力,深度集成Dubbo、Nacos、Sentinel等微服务技术栈,能够帮助用户极大的降低网关的部署及运维成本且能力不打......
  • 从零开始:直播电商APP开发全流程解析
    本篇文章,小编将从零开始,全面解析直播电商APP的开发流程,涵盖了关键的技术要点和开发阶段的关键步骤。 第一阶段:需求分析与规划此阶段的关键任务包括:1.用户需求调研2.功能规划3.技术选型第二阶段:设计与原型设计阶段是将需求转化为可执行计划的关键环节。在这一阶段,团队需要完成以下......