首先一樣先行討論 "群眾募資" 應用於 "智能合約" 的優點
由於虛擬貨幣本身不具有國域 任何人均可以購買
不會像是一般網路購物 限制於必須要有那些國家的銀行帳戶
智能合約的自動執行 提供了保障 確保過程的合理性
尤其對於股權型或是借貸型 擁有不可篡改的紀錄顯得格外重要
資訊不受限於平台 避免平台對於資訊的濫用
因此使用智能合約進行群眾募資是有一定的優勢的
聽起來就很誘人吧~ 感覺是個不錯的應用。
接下來本篇就依照以下重點進行敘述:
另外關於此篇的GitHub 專案位置
https://github.com/weiawesome/hardhat_tutorial
基本上是提供一個 可以放基本資訊與提案的位置
提案者成立自己的專案 整個募資過程就在自己的智能合約執行
提案者對於自己的提案擁有相對的控制權
平台不會過多干涉 集資的過程 一切都由"智能合約"所控制
群眾募資是一種新興的募款方式
基本上它是結合了 團購 與 預購 的概念
透過群眾募資
既讓提案者擁有資金去研發、生產
也能讓提案者觀察市場聲量、偏好等等資訊
臺灣上 較人為熟知的例子像是 嘖嘖、挖貝 等等。
以分類來說
接下來合約的撰寫 會去實現群眾募資的效果
// ./contracts/CrowdFunding.sol
/// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract CrowdFunding {
// 平台擁有者
address public owner;
// 提案事件
event ProposalEvent(address indexed addr,address indexed contractAddress,string title);
// 個人資料事件
event UserInfoEvent(address indexed addr,string name,string info);
// 建構子
constructor(){
owner=msg.sender;
}
// 提案
function propose(string memory _title) public payable{
// 提供基本資金用來建立子合約
require(msg.value>0,"The proposal need a fundamental asset.");
Proposal p=new Proposal{value: msg.value}(msg.sender,_title);
// 新增提案事件
emit ProposalEvent(msg.sender,address(p),_title);
}
// 激活子合約
function activateProposal(address _contractAddr) public{
Proposal proposal = Proposal(_contractAddr);
proposal.activateProposal(msg.sender);
}
// 更新個人資料
function updatePersonalInfo(string memory _name,string memory _info) public{
emit UserInfoEvent(msg.sender,_name,_info);
}
}
// ./contracts/CrowdFunding.sol
contract Proposal{
}
// 專案種類
enum ProposalType{
LendingBased,
EquityBased,
DonationBased,
RewardBased
}
// 專案內容分類
enum ProposalClassification{
Social,
Multimedia,
Entertainment,
Publishing,
Lifestyle,
Design,
Technology,
Leisure,
Other
}
// 專案處理募款的方式
enum ProposalReturnSystem{
AllOrNothing,
ForProposer
}
// 專案提供的募資方案
struct Plan {
// 方案名稱
string title;
// 方案內容
string content;
// 方案提供的數量
uint quantity;
// 方案的價格
uint price;
// 方案是否存在
bool exist;
}
// 平台擁有者
address public platformOwner;
// 專案擁有者
address payable public owner;
// 專案名稱
string public proposalTitle;
// 專案細節介紹
string public info;
// 募款金額
uint public contractAmount;
// 目標金額
uint public goalAmount;
// 募款期限
uint public campaignDuration;
// 方案們
string[] public plansTitle;
mapping(string => Plan) public plans;
// 各種分類
ProposalType public pt;
ProposalClassification public pc;
ProposalReturnSystem public pr;
// 提供贊助的紀錄(All Or Nothing 才需紀錄)
mapping(address => uint) public backRecord;
address[] public backers;
// 合約激活與否
bool public contractValid;
// 捐贈事件
event BackEvent(address addr,string _title,uint amount);
// 專案結算 募款金額流向紀錄
event SettlementEvent(address addr,string _title,uint amount);
constructor(address _owner,string memory _title) payable{
platformOwner=msg.sender;
owner=payable(_owner);
proposalTitle=_title;
contractValid=false;
}
// 唯有合約擁有者可以訪問
modifier onlyOwner() {
require(msg.sender == owner, "Only owner can call this function");
_;
}
// 唯有合約激活可以訪問
modifier onlyContractValid() {
require(contractValid, "The contract is invalid now");
_;
}
// 唯有募款期間可以訪問
modifier inFundingPeriod(){
require(block.timestamp<campaignDuration,"The crowd funding time is over.");
_;
}
// 唯有募款結束可以訪問
modifier outFundingPeriod(){
require(block.timestamp>campaignDuration,"The crowd funding time is not over.");
_;
}
// 修改 專案資訊(介紹資訊)
function editInfo(string memory _info) public onlyOwner{
info=_info;
}
// 新增 募款方案
function editPlan(string memory _title,string memory _content,uint _quantity,uint _price) public onlyOwner{
require(plans[_title].exist==false,"The plan has been exist.");
require(_price>0,"The price must be large then 0.");
plansTitle.push(_title);
plans[_title]=Plan(_title,_content,_quantity,_price,true);
}
// 設定 募款金額
function editGoalAmount(uint _amount) public onlyOwner{
require(_amount>0,"The GoalAmount must be large than 0.");
goalAmount=_amount;
}
// 設定 募款期限
function editCampaignDuration(uint _time) public onlyOwner{
require(_time>block.timestamp,"The time must be in the future.");
campaignDuration=_time;
}
// 設定專案種類
function editType(ProposalType _pt,ProposalClassification _pc,ProposalReturnSystem _pr) public onlyOwner{
pt=_pt;
pc=_pc;
pr=_pr;
}
// 獲取專案名稱
function getTitle() public view returns(string memory){
return proposalTitle;
}
// 獲取專案資訊(介紹)
function getInfo() public view returns(string memory) {
return info;
}
// 獲取專案可贊助的方案
function getPlans() public view returns(Plan[] memory) {
Plan[] memory result = new Plan[](plansTitle.length);
uint index = 0;
for (uint i = 0; i < plansTitle.length; i++) {
string memory _title=plansTitle[i];
Plan storage plan = plans[_title];
if (plan.exist==true){
result[index]=plan;
index++;
}
}
return result;
}
// 獲取目標募款金額
function getGoalAmount() public view returns(uint) {
return goalAmount;
}
// 獲取專案各項數據種類
function getType() public view returns(ProposalType,ProposalClassification,ProposalReturnSystem){
return (pt,pc,pr);
}
// 獲取當前募款到的金額
function getAmount() public view returns(uint) {
return contractAmount;
}
// 獲取募款期限
function getCampaignDuration() public view returns(uint) {
return campaignDuration;
}
// 根據方案名稱進行贊助
function backPlan(string memory _title) public payable onlyContractValid inFundingPeriod{
require(plans[_title].exist==true,"The plan is not exist.");
require(plans[_title].quantity>0,"The plan is sold out.");
require(msg.value==plans[_title].price,"The value is not equal to the plan's price.");
if (pr==ProposalReturnSystem.AllOrNothing){
if (backRecord[msg.sender]==0){
backers.push(msg.sender);
}
backRecord[msg.sender]+=msg.value;
}
contractAmount+=msg.value;
plans[_title].quantity-=1;
emit BackEvent(msg.sender,_title,msg.value);
}
// 根據類型不同進行不同的處理
function settleProposal() public outFundingPeriod{
if (pr==ProposalReturnSystem.AllOrNothing){
if (contractAmount>=goalAmount){
uint remainAmount=address(this).balance;
owner.transfer(remainAmount);
emit SettlementEvent(owner,proposalTitle,remainAmount);
} else{
for (uint i=0;i<backers.length;i++){
address payable addr=payable(backers[i]);
addr.transfer(backRecord[backers[i]]);
emit SettlementEvent(addr,proposalTitle,backRecord[backers[i]]);
backRecord[backers[i]]=0;
}
uint remainAmount=address(this).balance;
owner.transfer(remainAmount);
emit SettlementEvent(owner,proposalTitle,remainAmount);
}
} else if (pr==ProposalReturnSystem.ForProposer){
uint remainAmount=address(this).balance;
owner.transfer(remainAmount);
emit SettlementEvent(owner,proposalTitle,remainAmount);
}
contractValid=false;
}
// 做基本的檢查 最後再激活合約
function activateProposal(address addr) public{
require(addr==owner,"Only owner can activate the proposal");
require((bytes(info)).length!=0,"The information can't be empty.");
require(plansTitle.length!=0,"The plans can't be empty.");
require(goalAmount!=0,"The goal amount can't be 0.");
require(campaignDuration>block.timestamp,"The campaignDuration can't be in the past.");
contractValid=true;
}
// ./scripts/crowdFunding/deploy.ts
import { ethers } from "hardhat";
async function main() {
const lock = await ethers.deployContract("CrowdFunding");
await lock.waitForDeployment();
console.log(
`The CrowdFunding platform is deployed to ${lock.target}`
);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
# 部署 群眾募資 智能合約
npx hardhat run .\scripts\crowdFunding\deploy.ts --network ganache
在互動上 分為三個環節講述
import { ethers } from 'hardhat';
import {Contract} from "ethers";
import {PlatformContractAddress} from "../parameters";
async function main() {
const abi = require("../../../artifacts/contracts/CrowdFunding.sol/CrowdFunding.json").abi;
const [deployer] = await ethers.getSigners();
console.log("User's Address: ",deployer.address)
const contractAddress = PlatformContractAddress;
const contract = new Contract(contractAddress, abi, deployer);
const amount = ethers.parseEther("10");
const title="Tcweeei Proposal Title"
await contract.propose(title,{ value: amount }).then(async (result) => {
console.log("Result: ", result);
console.log('Submit the proposal successfully');
}).catch((e)=>{
console.log("Error: ",e);
})
}
main()
.then(() => process.exit(0))
.catch(error => {
console.error(error);
process.exit(1);
});
執行結果:
import {ethers} from "hardhat";
import {PlatformContractAddress} from "../parameters";
async function main() {
const [deployer]=await ethers.getSigners();
const contractAddress = PlatformContractAddress
const contractABI = require("../../../artifacts/contracts/CrowdFunding.sol/CrowdFunding.json").abi;
const contract = new ethers.Contract(contractAddress, contractABI, deployer);
const filter = contract.filters.ProposalEvent(deployer.address,null,null);
const events = await contract.queryFilter(filter);
for (let i = 0; i < events.length; i++) {
// @ts-ignore
console.log("Index: ",i," Value: ",events[i].args)
}
}
main().then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
執行結果:
import { ethers } from 'hardhat';
import {Contract} from "ethers";
import {PlatformContractAddress} from "../parameters";
async function main() {
const abi = require("../../../artifacts/contracts/CrowdFunding.sol/CrowdFunding.json").abi;
const [deployer] = await ethers.getSigners();
console.log("User's Address: ",deployer.address)
const contractAddress = PlatformContractAddress;
const contract = new Contract(contractAddress, abi, deployer);
await contract.updatePersonalInfo("Tcweeei","I'm a handsome boy.").then(async (result) => {
console.log("Result: ", result);
console.log('Update the personal information successfully');
}).catch((e)=>{
console.log("Error: ",e);
})
}
main()
.then(() => process.exit(0))
.catch(error => {
console.error(error);
process.exit(1);
});
執行結果:
import {ethers} from "hardhat";
import {PlatformContractAddress} from "../parameters";
async function main() {
const [deployer]=await ethers.getSigners();
const contractAddress = PlatformContractAddress
const contractABI = require(".././../../artifacts/contracts/CrowdFunding.sol/CrowdFunding.json").abi;
const contract = new ethers.Contract(contractAddress, contractABI, deployer);
const filter = contract.filters.UserInfoEvent(deployer.address,null,null);
const events = await contract.queryFilter(filter);
for (let i = 0; i < events.length; i++) {
// @ts-ignore
console.log("Index: ",i," Value: ",events[i].args)
}
}
main().then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
執行結果:
import { ethers } from 'hardhat';
import {Contract} from "ethers";
import {PlatformContractAddress, ProposalContractAddress} from "../parameters";
async function main() {
const abi = require("../../../artifacts/contracts/CrowdFunding.sol/CrowdFunding.json").abi;
const [deployer] = await ethers.getSigners();
console.log("User's Address: ",deployer.address)
const contractAddress = PlatformContractAddress;
const contract = new Contract(contractAddress, abi, deployer);
await contract.activateProposal(ProposalContractAddress).then(async (result) => {
console.log("Result: ", result);
console.log('Activate the proposal successfully');
}).catch((e)=>{
console.log("Error: ",e);
})
}
main()
.then(() => process.exit(0))
.catch(error => {
console.error(error);
process.exit(1);
});
執行結果:
import { ethers } from 'hardhat';
import {Contract} from "ethers";
import { ProposalContractAddress} from "../parameters";
async function main() {
const abi = require("../../../artifacts/contracts/CrowdFunding.sol/Proposal.json").abi;
const [deployer] = await ethers.getSigners();
console.log("User's Address: ",deployer.address)
const contractAddress = ProposalContractAddress;
const contract = new Contract(contractAddress, abi, deployer);
// 設定專案資訊(簡介)
const proposalInformation="This is the information ot introduction to the proposal.\nMade by Tcweeei.";
await contract.editInfo(proposalInformation).then(async (result) => {
console.log('Update the personal information successfully');
}).catch((e)=>{
console.log("Error: ",e);
})
// 設定目標金額
const amount = ethers.parseEther("10");
await contract.editGoalAmount(amount).then(async (result) => {
console.log('Edit the goal amount with ',amount,'successfully');
}).catch((e)=>{
console.log("Error: ",e);
})
// 設定募款期限
const currentTimestamp = Math.round(Date.now() / 1000);
const campaignDuration=currentTimestamp+60*10;
await contract.editCampaignDuration(campaignDuration).then(async (result) => {
console.log('Update Campaign Duration with ',campaignDuration,' successfully');
}).catch((e)=>{
console.log("Error: ",e);
})
// 設定募款專案分類
const pt=3; // RewardBased
const pr=0; // Social
const pc=1; // ForProposer
await contract.editType(pt,pr,pc).then(async (result) => {
console.log('Update the type successfully');
}).catch((e)=>{
console.log("Error: ",e);
})
// 設定募資的方案
const title="方案一"
const content="This is a plan need to 10 ETH and limit to 10.";
const quantity=10
const price = ethers.parseEther("10");
await contract.editPlan(title,content,quantity,price).then(async (result) => {
console.log('Add the plan with title ',title,' successfully');
}).catch((e)=>{
console.log("Error: ",e);
})
}
main()
.then(() => process.exit(0))
.catch(error => {
console.error(error);
process.exit(1);
});
執行結果:
import { ethers } from 'hardhat';
import {Contract} from "ethers";
import { ProposalContractAddress} from "../parameters";
async function main() {
const abi = require("../../../artifacts/contracts/CrowdFunding.sol/Proposal.json").abi;
const [deployer] = await ethers.getSigners();
console.log("User's Address: ",deployer.address)
const contractAddress = ProposalContractAddress;
const contract = new Contract(contractAddress, abi, deployer);
await contract.settleProposal().then(async (result) => {
console.log('Settle successfully');
}).catch((e)=>{
console.log("Error: ",e);
})
}
main()
.then(() => process.exit(0))
.catch(error => {
console.error(error);
process.exit(1);
});
執行結果:
import {ethers} from "hardhat";
import {PlatformContractAddress, ProposalContractAddress} from "../parameters";
async function main() {
const [deployer]=await ethers.getSigners();
const contractAddress = ProposalContractAddress
const contractABI = require("../../../artifacts/contracts/CrowdFunding.sol/Proposal.json").abi;
const contract = new ethers.Contract(contractAddress, contractABI, deployer);
const filter = contract.filters.SettlementEvent();
const events = await contract.queryFilter(filter);
for (let i = 0; i < events.length; i++) {
// @ts-ignore
console.log("Index: ",i," Value: ",events[i].args)
}
}
main().then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
執行結果:
import { ethers } from 'hardhat';
import {Contract} from "ethers";
import { ProposalContractAddress} from "../parameters";
async function main() {
const abi = require("../../../artifacts/contracts/CrowdFunding.sol/Proposal.json").abi;
const [deployer,user_with_index1] = await ethers.getSigners();
console.log("User's Address: ",user_with_index1.address)
const contractAddress = ProposalContractAddress;
console.log("\nThe Proposal information:\n");
const contract = new Contract(contractAddress, abi, user_with_index1);
await contract.getTitle().then(async (result) => {
console.log("Title: ", result);
}).catch((e)=>{
console.log("Error: ",e);
})
console.log("\n")
await contract.getInfo().then(async (result) => {
console.log("Information: ", result);
}).catch((e)=>{
console.log("Error: ",e);
})
await contract.getType().then(async (result) => {
console.log("Type: ", result);
}).catch((e)=>{
console.log("Error: ",e);
})
console.log("\n");
await contract.getPlans().then(async (result) => {
console.log("Plans: ", result);
}).catch((e)=>{
console.log("Error: ",e);
})
console.log("\n");
await contract.getCampaignDuration().then(async (result) => {
console.log("CampaignDuration: ", result);
}).catch((e)=>{
console.log("Error: ",e);
})
console.log("\n");
await contract.getAmount().then(async (result) => {
console.log("CurrentAmount: ", result);
}).catch((e)=>{
console.log("Error: ",e);
})
await contract.getGoalAmount().then(async (result) => {
console.log("GoalAmount: ", result);
}).catch((e)=>{
console.log("Error: ",e);
})
}
main()
.then(() => process.exit(0))
.catch(error => {
console.error(error);
process.exit(1);
});
執行結果:
import { ethers } from 'hardhat';
import {Contract} from "ethers";
import { ProposalContractAddress} from "../parameters";
async function main() {
const abi = require("../../../artifacts/contracts/CrowdFunding.sol/Proposal.json").abi;
const [deployer,user_with_index1] = await ethers.getSigners();
console.log("User's Address: ",user_with_index1)
const contractAddress = ProposalContractAddress;
const contract = new Contract(contractAddress, abi, user_with_index1);
const amount = ethers.parseEther("10");
const title="方案一"
await contract.backPlan(title,{value:amount}).then(async (result) => {
console.log("Result: ", result);
console.log('Back the plan successfully');
}).catch((e)=>{
console.log("Error: ",e);
})
}
main()
.then(() => process.exit(0))
.catch(error => {
console.error(error);
process.exit(1);
});
執行結果:
import {ethers} from "hardhat";
import {PlatformContractAddress, ProposalContractAddress} from "../parameters";
async function main() {
const [deployer]=await ethers.getSigners();
const contractAddress = ProposalContractAddress
const contractABI = require("../../../artifacts/contracts/CrowdFunding.sol/Proposal.json").abi;
const contract = new ethers.Contract(contractAddress, contractABI, deployer);
const filter = contract.filters.BackEvent();
const events = await contract.queryFilter(filter);
for (let i = 0; i < events.length; i++) {
// @ts-ignore
console.log("Index: ",i," Value: ",events[i].args)
}
}
main().then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
執行結果:
從 Ganache 觀察:
GitHub 專案位置: https://github.com/weiawesome/hardhat_tutorial
有感受到智能合約的優勢了嗎?
去中心化、自動化、公平公正、跨國性...
Q. 不過完全的去中心化 一定是優點嗎?
那不一定完全是優點
平台還是必須對提案的資訊內容做審查
不然會充斥詐騙 或是品質極差的提案
間接整個品牌的名譽就造就 非常糟的影響
最後的最後希望透過這篇能理解
想目前對於智能合約 如何部署撰寫 應該有基本觀念了
很能感受到去中心化的美好了吧~~~
講完合約 當然要來講如何應用囉!
終於終於要進入最後的篇章了
接下來就來建立一款 "去中心化應用程序(DApp)"