目录
这是一个超超超级简单的智能合约提案项目,你确定不点进来看一下吗?
这是一个超超超级简单的智能合约提案项目,你确定不点进来看一下吗?
引言:
作为一个"新手"写智能合约,我选择做提案项目的目的是学习和成长。在这个项目中,我计划使用Solidity编程语言来编写智能合约,并进行部署和测试。我将设计一个简单而有趣的提案系统,允许用户提交提案并进行投票。我将利用前端技术,如HTML、CSS和JavaScript,来开发一个直观且易于使用的界面。通过这个项目,我希望能够提高我的编程能力和设计技巧。
1、搭建开发环境:
1.安装并配置必要的开发工具,如Node.js、Truffle Suite或Hardhat等。
2.部署一个本地的以太坊测试网络(如Ganache,Geth)或连接到以太坊的公共测试网络(如Ropsten)。
3.使用Remix,Remix是一个在线的Solidity IDE,其提供了Solidity编译器、调试器和运行环境。
4.MetaMask 是一种加密货币钱包,使用户能够访问分散式应用程序(dapps)的 Web 3 生态系统。
简单来说:
-
MetaMask 是一种加密货币钱包,使用户能够存储以太币和其他 ERC-20 代币。
-
钱包还可用于与去中心化应用程序或 dapp 进行交互
2、编写智能合约:
编写提案智能合约及实现一个基于 ERC20 标准的代币合约。
定义合约的数据结构、状态变量、函数以及相关的事件。
javascript
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import"@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MyToken1 is ERC20 {
constructor(string memory name, string memory symbol) ERC20(name, symbol) {
_mint(msg.sender, 111 * 10**uint(decimals()));
}
function getDEcimals() public view returns (uint){
return decimals();
}
}
javascript
//SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import"@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract EthVoting {
//附议人信息
struct Voter {
uint voteTimeStamp; //投票时的区块时间
uint voterMoney;
bool initialized; //判断是否投过票的标志
}
//提案内容
struct Proposal {
string pName; //提案标题
string pCtx; //提案内容
address chairperson; //提案主持人
uint voteCount; //附议人数
bool initialized; //判断提案是否存在的标志
uint limitTime; //附议限制时间
uint money; //该提案的已经募集到的金额
uint r_money;//该提案目标募集的金额
mapping(address => Voter) voters; //附议列表
}
//提案内容-加入的数组中
struct Proposal_group {
string pName; //提案标题
string pCtx; //提案内容
address chairperson; //提案主持人
uint voteCount; //附议人数
bool initialized; //判断提案是否存在的标志
uint limitTime; //附议限制时间
uint money; //该提案的已经募集到的金额
uint r_money;//该提案目标募集的金额
}
//声明一个全局变量成为部署当前合约的调用者
address public master;
//声明一个全局变量成为master指定一个账户成为提案合法提取者
address receiver;
//ERC20的合约地址赋值给这个变量
address public ercaddress;
constructor (address ercaddress_ ) {
//实例化合约token合约对象
master=msg.sender;
//实例化一个token
ercaddress =ercaddress_;
}
//转ERC20代币
function transfer2other(address to_ ,uint ercnum_) public {
IERC20(ercaddress).transfer(to_,ercnum_*(10**18));
}
//获取某个ERC20代币余额
function getBalance() public view returns (uint256) {
return IERC20(ercaddress).balanceOf(address(this));
}
//提案编号
uint pId;
//函数修饰符,表示只有master账户才能执行的函数
modifier onlyMaster(){
require(msg.sender==master,"it must be master");
_;
}
//函数修饰符,表示只有指定账户才能提取提案里的钱
modifier onlyReceiver(){
require(msg.sender==receivers[msg.sender]&&msg.sender==receivers[msg.sender]&&msg.sender==receivers[msg.sender],"it must be receiver");
_;
}
//函数修饰符,表示只有指定账户才能进行提案的附议
modifier onlySomeDoVoting(){
require(msg.sender!=receiver,"it can not be receiver");
require(msg.sender!=master,"it can not be master");
_;
}
//所有提案列表
mapping(uint => Proposal) public proposals;
//提案余额提取者
mapping(address=>address) public receivers;
//所有提案列表-加入到数组中
mapping(uint => Proposal_group) public proposales;
//所有提案列表-加入到数组中
mapping(uint=>Proposal_group[]) public Proposal_groups;
//附议事件
event VoteEvt(string indexed eventType, address _voter, uint timestamp);
//提案事件
event ProposeEvt(string indexed eventType, uint _proposalId, uint _limitTime);
//创建新提案
function createProposal(string memory _pName, string memory _pCtx, uint _limitTime ,uint r_money) public payable returns (uint){
//赋值语句
pId=pId+1;
Proposal storage _proposal = proposals[pId];
_proposal.pName = _pName;
_proposal.pCtx = _pCtx;
_proposal.chairperson = msg.sender;
_proposal.initialized = true;
_proposal.limitTime=block.timestamp+ _limitTime;
_proposal.r_money= r_money;
_proposal.voteCount = 0;
//赋值并加入到数组中,以获取所有的提案
Proposal_group storage _proposales = proposales[pId];
_proposales.pName = _pName;
_proposales.pCtx = _pCtx;
_proposales.chairperson = msg.sender;
_proposales.initialized = true;
_proposales.limitTime=block.timestamp+ _limitTime;
_proposales.r_money= r_money;
_proposales.voteCount = 0;
Proposal_groups[1].push(_proposales);
emit ProposeEvt("propose", pId, _limitTime);
return pId;
}
//提案提取人数量
uint receiverCount;
//master添加提案提取人
function addReceiver(address voterAddr) public payable onlyMaster{
require(receiverCount<=3,"receiver can not exceed 3!");
receiverCount+=1;
receivers[voterAddr]=voterAddr;
}
//master删除提案提取人
function deleteReceiver(address voterAddr) public payable onlyMaster{
delete receivers[voterAddr];
receiverCount-=1;
}
//进行附议
function doVoting(uint pId1) public payable {
if (proposals[pId1].chairperson == msg.sender)
revert("proposal person is not youself");
if (msg.value <2)
revert("money is lower than 2");
//提案是否存在
if (proposals[pId1].initialized == false)
revert("proposal not exist");
uint currentTime = block.timestamp;
//是否已超过提案时限
if (proposals[pId1].limitTime < currentTime)
revert("exceed voting time");
//是否已经投过票
if (proposals[pId1].voters[msg.sender].initialized == true)
revert("already vote");
if (proposals[pId1].money>=proposals[pId1].r_money)
revert("money is enough!");
//新投票信息
Voter memory voter = Voter({
voteTimeStamp: block.timestamp,
initialized: true,
voterMoney:msg.value
});
//记录投票信息
proposals[pId1].voters[msg.sender] = voter;
proposals[pId1].voteCount+=1;
proposals[pId1].money+=msg.value;
emit VoteEvt("vote", msg.sender, block.timestamp);
}
//提案里的钱由合法提取者提取出来
function collectMoney(uint pId1) public payable onlyReceiver{
payable(receiver).transfer(proposals[pId1].money);
proposals[pId1].money=0;
delete proposals[pId1];
}
//查询是否附议
function queryVoting(uint pId1, address voterAddr) public view returns (uint,uint){
//提案是否存在
if (proposals[pId1].initialized == false)
revert("proposal not exist");
//返回投票时间和和金额
return (proposals[pId1].voters[voterAddr].voteTimeStamp,proposals[pId1].voters[voterAddr].voterMoney);
}
//获取区块链时间
function getBlockTime() public view returns (uint t) {
t = block.timestamp;
}
// //查询所有提案
function getProposals() public view returns (Proposal_group[] memory) {
return Proposal_groups[1];
}
// 查询附议提案
function queryP_dovot(uint pid) public view returns (uint,uint ) {
return (proposals[pid].voteCount,proposals[pid].money);
}
//查询提案标题
function getProposalName(uint pId1) public view returns (string memory s) {
s = proposals[pId1].pName;
}
//查询提案内容
function getProposalCtx(uint pId1) public view returns (string memory s) {
s = proposals[pId1].pCtx;
}
//查询提案内容
function getProposalVCnt(uint pId1) public view returns (uint v) {
v = proposals[pId1].voteCount;
}
//查询提案期限
function getProposalLimit(uint pId1) public view returns (uint t) {
t = proposals[pId1].limitTime;
}
}
3、部署智能合约:
编译智能合约代码,生成合约的字节码和ABI(Application Binary Interface)。
将合约部署到所选择的以太坊网络中。
4、编写前端交互代码(使用web3.js):
创建一个前端应用程序,可以是基于HTML/CSS/JavaScript的简单界面。
使用web3.js库与部署的智能合约进行交互,包括调用合约方法、处理交易等。
配置MetaMask或其他以太坊钱包插件,使用户可以通过浏览器与智能合约交互并进行以太币交易。
具体功能见代码注释
javascript
const web3 = new Web3(Web3.givenProvider);
const abi = [];
let changeAccount = [];
var myContract = new web3.eth.Contract(
abi,
"0xa9dC761F2B9986Ce04694c3f279f0d62bb82A9CA"
);
let Accounts = [];
console.log(web3);
web3.eth.getAccounts().then(function (accounts) {
Accounts = accounts;
});
ethereum.on("accountsChanged", function (account) {
changeAccount = account;
});
function handle() {
var Title = document.getElementById("Title").value;
var content = document.getElementById("content").value;
var time = document.getElementById("time").value;
var eth = document.getElementById("eth").value;
var start = new Date("1970-01-01");
var end = new Date(time);
var t_start = start.getTime();
var t_end = end.getTime();
var days = (t_end - t_start) / 1000 / 60 / 60 / 24; //10
var s = days * 24 * 60 * 60;
//添加提案
myContract.methods
._createProposal(Title, content, s, eth)
.send({ from: changeAccount[0], gas: 3000000 })
.then(function (receipt) {
alert("提案添加成功");
});
}
//查询提案列表
function queryall() {
myContract.methods
._getProposals()
.call({ from: changeAccount[0] })
.then(function (receipt) {
console.log(receipt);
});
}
var personAdd = [];
//添加提案余额提取人
function handle1() {
var Person = document.getElementById("Person").value;
var Person1 = document.getElementById("Person1").value;
console.log(Person == "");
console.log(Person1 == "");
if (Person != "") {
personAdd[0] = Person;
myContract.methods
._addReceiver(personAdd[0])
.send({ from: changeAccount[0], gas: 3000000 })
.then(function (receipt) {
alert("添加提案余额提取人成功");
});
}
if (Person1 != "") {
personAdd[0] = Person1;
myContract.methods
._deleteReceiver(personAdd[0])
.send({ from: changeAccount[0], gas: 3000000 })
.then(function (receipt) {
alert("删除提案余额提取人成功");
});
}
}
//附议提案
function handle2() {
var id = document.getElementById("id").value;
var eth = document.getElementById("eth").value;
web3.eth.getAccounts().then(function (accounts) {
Accounts = accounts;
});
myContract.methods
._doVoting(id)
.send({ from: changeAccount[0], gas: 3000000, value: eth })
.then(function (receipt) {
alert("提案附议成功");
var amount = 10;
myContract.methods
._transfer2other(Accounts[0], amount)
.send({ from: changeAccount[0], gas: 3000000 })
.then(function (receipt) {
alert("附议成功,该账户获得10个ERC20代币");
});
});
}
//receiver接收提案中的金额
function handle3() {
web3.eth.getAccounts().then(function (accounts) {
Accounts = accounts;
});
var id = document.getElementById("id").value;
myContract.methods
._collectMoney(id)
.send({ from: changeAccount[0], gas: 3000000 })
.then(function (receipt) {
alert("receiver提取成功");
});
}
//查询提案附议结果
function handle4() {
web3.eth.getAccounts().then(function (accounts) {
Accounts = accounts;
});
var id = document.getElementById("id").value;
var id1 = document.getElementById("id1").value;
var Address = document.getElementById("Address").value;
if (id != "") {
myContract.methods
._queryP_dovot(id)
.call({ from: changeAccount[0] })
.then(function (receipt) {
alert("查询成功");
console.log(receipt);
});
}
if (id1 != "") {
//查询某人附议结果(pid,address)
personAdd[0] = Address;
myContract.methods
._queryVoting(id1, personAdd[0])
.call({ from: changeAccount[0] })
.then(function (receipt) {
alert("查询成功");
console.log(receipt);
});
}
}
5、设计关于提案平台的html:
1、是的,HTML代码我没给全,比如设置提案接收人,提案附议页面,查询提案页面我都没给,看到这个文章的有缘人,你们要靠你们自己了!
html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>首页</title>
<!--bootstrap css 挺方便简洁的一款第三方前端开发框架-->
<link rel="stylesheet" href="css/bootstrap.css" />
<link rel="stylesheet" href="css/style.css" />
</head>
<body>
<div class="span12 content" style="width: 1180px;">
<div class="box" style=" margin-top: 166px;
margin-left: 245px;">
<div class="box-head">
<h3>公告提案平台</h3>
<a href="/setReveiver.html"><button>设置提案接收人</button></a>
<a href="/dovot.html"><button>附议页面</button></a>
<a href="/searchPid.html"><button>查询提案</button></a>
<a href="/reveiver.html"><button>余额提取页面</button></a>
<button onclick="queryall()">查询提案列表(控制台查看)</button>
</div>
<div class="box-content">
<div class="form-horizontal">
<div class="control-group">
<label class="control-label">标题</label>
<div class="controls">
<div class="input-append">
<input id="csyTitle" type="text" class="tip" />
<!-- <span class="tip add-on" id="jieshouren"> -->
<!-- <i class="icon-user" style="cursor:pointer" ></i> -->
<!-- <div style="display:none" id="selectlxr"></div> -->
</span>
</div>
</div>
</div>
<div class="control-group">
<label class="control-label">内容</label>
<div class="controls">
<textarea id="csycontent" class="span6 input-square"></textarea>
</div>
</div>
<div class="control-group">
<label class="control-label">截止时间</label>
<div class="controls">
<input id="csytime" type="datetime-local" />
</div>
</div>
<div class="control-group">
<label class="control-label">所需费用</label>
<div class="controls">
<input type="text" id="csyeth" class="tip" />
</div>
</div>
<div class="control-group">
<label class="control-label"> </label>
<div class="controls">
<input type="button" class="btn btn-fo" onclick="handle()" value="新建提案" />
</div>
</div>
</div>
</div>
</div>
</div>
<script src="web3.min.js"></script>
<script src="app1.js"></script>
<script src="js/jQuery.js"></script>
<script src="js/jquery.artDialog.js?skin=idialog"></script>
<!--js结束-->
<script>
//循环输出创建十个复选框
var chtml = "";
for (var i = 0; i < 10; i++) {
chtml += "<div style='word-wrap:break-word; width:450px; '>";
chtml += '<label style="float:left;padding:15px"><input type="checkbox" name="aaa" value="1" class="{required:true}" /><span style="margin-left:10px">小'+i+'</span></label>';
chtml += "</div>";
}
//把得到字符串利用jquery添加到元素里面生成checkbox
$("#selectlxr").html(chtml);
//创建一个 dialog弹出框(第三方插件有兴趣可以看下 超赞的一款插件 http://www.planeart.cn/demo/artDialog/) 把创建好的弹出框隐藏起来
var dia = $.dialog(
{
title: "选择联系人", width: "500px",
content: $("#selectlxr").html(),
close: function () {
this.hide();
return false;
},
follow: document.getElementById("jieshouren")
}
).hide();
//点击 显示
$("#jieshouren").click(function () {
dia.show();
})
//事件 获取checkbox点击时候的父元素的值 添加到text 如果点击收的选中状态为checked 则添加 否则 删除
$("input[type=checkbox]").click(function () {
try {
if ($(this).attr("checked")) {
$("#jsrtxt").val($("#jsrtxt").val() + $(this).parent().text() + ";");
} else {
$("#jsrtxt").val($("#jsrtxt").val().replace($(this).parent().text() + ';', ""));
}
} catch (e) {
$("#jsrtxt").val("");
}
})
//初步测试 暂无小bug 可以为text增加一个只读
</script>
</body>
</html>