bitpro가 원문에서 글자만 모은 내용이에요. 표·그림·수식은 빠지거나 깨질 수 있어요. 전체 41쪽 중 14쪽
ds
GetProposal
: Fetch proposal by ID
GetMemberCount
: Fetch member count
GetMemberExist
: Check if a member exists by address
4.
Message Definitions
Member
: DAO member (address)
Proposal
: Proposal (title, description, votes, status, vote threshold)
CreateProposalInput
: Fields for creating a proposal (title, description, vote threshold)
VoteInput
: Fields for voting on a proposal (proposal ID, vote)
MemberList
: List of DAO members
ProposalList
: List of proposals
Defining Contract State
Open the
src/BuildersDAOState.cs
file.
Replace its contents with this code snippet.
src/BuildersDAOState.cs
using
System
.
Collections
.
Generic
;
using
System
.
Diagnostics
.
CodeAnalysis
;
using
AElf
.
Sdk
.
CSharp
.
State
;
using
AElf
.
Types
;
namespace
AElf
.
Contracts
.
BuildersDAO
{
// The state class is access the blockchain state
public
class
BuildersDAOState
:
ContractState
{
public
BoolState
Initialized
{
get
;
set
;
}
public
MappedState
<
Address
,
bool
>
Members
{
get
;
set
;
}
public
MappedState
<
string
,
Proposal
>
Proposals
{
get
;
set
;
}
public
Int32State
MemberCount
{
get
;
set
;
}
public
Int32State
NextProposalId
{
get
;
set
;
}
}
}
Understanding the Code
3.
State Variables
Members
: Mapping each member to a boolean indicates if they joined the DAO
Proposals
: Mapping each proposal to an ID for identification and retrieval
MemberCountId
and
NextProposalId
: Track total number of members and proposals
Next Step
Implement the logic of our voting smart contract.
Implement Voting Smart Contract Logic
Checking Smart Contract Logics
Open
src/BuildersDAO.cs
Replace the existing content with this code snippet.
src/BuildersDAO.cs
using
System
.
Collections
.
Generic
;
using
System
.
Security
.
Principal
;
using
AElf
.
Sdk
.
CSharp
;
using
AElf
.
Sdk
.
CSharp
.
State
;
using
AElf
.
Types
;
using
Google
.
Protobuf
.
WellKnownTypes
;
namespace
AElf
.
Contracts
.
BuildersDAO
{
public
class
BuildersDAO
:
BuildersDAOContainer
.
BuildersDAOBase
{
const
string
author
=
"REPLACE PLACEHOLDER HERE"
;
// Implement Initialize Smart Contract Logic
public
override
Empty
Initialize
(
Empty
input
)
{
}
// Implement Join DAO Logic
public
override
Empty
JoinDAO
(
Address
input
)
{
}
// Implement Create Proposal Logic
public
override
Proposal
CreateProposal
(
CreateProposalInput
input
)
{
}
// Implement Vote on Proposal Logic
public
override
Proposal
VoteOnProposal
(
VoteInput
input
)
{
}
// Implement Get All Proposals Logic
public
override
ProposalList
GetAllProposals
(
Empty
input
)
{
}
// Implement Get Proposal Logic
public
override
Proposal
GetProposal
(
StringValue
input
)
{
}
// Implement Get Member Count Logic
public
override
Int32Value
GetMemberCount
(
Empty
input
)
{
}
// Implement Get Member Exist Logic
public
override
BoolValue
GetMemberExist
(
Address
input
)
{
}
}
}
danger
Aelf dAppChain does not allow duplicate identical smart contracts. Hence, we will be using the author variable as the unique identifier for our voting smart contract in order to deploy the smart contract successfully.
Implementing Initialize Function
Go to the comment
Implement Vote on Proposal Logic
.
Check if the smart contract is already initialized; return if true.
Define a hardcoded proposal with necessary parameters.
Update the Proposals state variable with the hardcoded proposal and increment the proposalId.
src/BuildersDAO.cs
// Implement Initialize Smart Contract Logic
public
override
Empty
Initialize
(
Empty
input
)
{
Assert
(
!
State
.
Initialized
.
Value
,
"already initialized"
)
;
var
initialProposal
=
new
Proposal
{
Id
=
"0"
,
Title
=
"Proposal #1"
,
Description
=
"This is the first proposal of the DAO"
,
Status
=
"IN PROGRESS"
,
VoteThreshold
=
1
,
}
;
State
.
Proposals
[
initialProposal
.
Id
]
=
initialProposal
;
State
.
NextProposalId
.
Value
=
1
;
State
.
MemberCount
.
Value
=
0
;
State
.
Initialized
.
Value
=
true
;
return
new
Empty
(
)
;
}
Implementing Join DAO Function
Go to the comment
Implement Join DAO Logic
Check if the member already exists in the DAO using the
Members
state variable.
If not found, update
Members
to include the user's address.
Increment
membersCount
to reflect the new member added.
You'll implement this function. Once done, you can proceed to the next page to compare your code with the reference implementation.
src/BuildersDAO.cs
// Implement Join DAO Logic
public
override
Empty
JoinDAO
(
Address
input
)
{
// Based on the address, determine whether the address has joined the DAO. If it has, throw an exception
// If the address has not joined the DAO, then join and update the state's value to true
// Read the value of MemberCount in the state, increment it by 1, and update it in the state
// Using 'return null' to ensure the contract compiles successfully. Please update it to the correct return value when implementing
return
null
;
}
Implementing Create Proposal Function
Go to the comment
Implement Create Proposal Logic
Check if the user is a DAO member (required to create proposals).
Create a new proposal object using fields from
CreateProposalInput
.
Update
Proposals with the new proposal, increment NextProposalId, and return the created proposal object.
Now, use the provided code snippet to fill in the
CreateProposal
function.
src/BuildersDAO.cs
// Implement Create Proposal Logic
public
override
Proposal
CreateProposal
(
CreateProposalInput
input
)
{
Assert
(
State
.
Members
[
input
.
Creator
]
,
"Only DAO members can create proposals"
)
;
var
proposalId
=
State
.
NextProposalId
.
Value
.
ToString
(
)
;
var
newProposal
=
new
Proposal
{
Id
=
proposalId
,
Title
=
input
.
Title
,
Description
=
input
.
Description
,
Status
=
"IN PROGRESS"
,
VoteThreshold
=
input
.
VoteThreshold
,
YesVotes
=
{
}
,
// Initialize as empty
NoVotes
=
{
}
,
// Initialize as empty
}
;
State
.
Proposals
[
proposalId
]
=
newProposal
;
State
.
NextProposalId
.
Value
+=
1
;
return
newProposal
;
// Ensure return
}
Implementing Vote On Proposal Function
Go to the comment
Implement Vote on Logic
Perform these checks:
Verify if the member is a DAO member (required to vote).
Confirm if the proposal exists; otherwise, display an error message.
Check if the member has already voted on the proposal; members can vote only once.
If all checks pass, store the member’s vote and update the proposal state.
Update the proposal status based on vote thresholds:
If
yesVotes
reach the threshold, update status to "PASSED".
If
noVotes
reach the threshold, update status to "DENIED".
Now, use the provided code snippet to complete the
VoteOnProposal
function.
src/BuildersDAO.cs
// Implement Vote on Proposal Logic
public
override
Proposal
VoteOnProposal
(
VoteInput
input
)
{
Assert
(
State
.
Members
[
input
.
Voter
]
,
"Only DAO members can vote"
)
;
var
proposal
=
State
.
Proposals
[
input
.
ProposalId
]
;
// ?? new proposal
Assert
(
proposal
!=
null
,
"Proposal not found"
)
;
Assert
(
!
proposal
.
YesVotes
.
Contains
(
input
.
Voter
)
&&
!
proposal
.
NoVotes
.
Contains
(
input
.
Voter
)
,
"Member already voted"
)
;
// Add the vote to the appropriate list
if
(
input
.
Vote
)
{
proposal
.
YesVotes
.
Add
(
input
.
Voter
)
;
}
else
{
proposal
.
NoVotes
.
Add
(
input
.
Voter
)
;
}
// Update the proposal in state
State
.
Proposals
[
input
.
ProposalId
]
=
proposal
;
// Check if the proposal has reached its vote threshold
if
(
proposal
.
YesVotes
.
Count
>=
proposal
.
VoteThreshold
)
{
proposal
.
Status
=
"PASSED"
;
}
else
if
(
proposal
.
NoVotes
.
Count
>=
proposal
.
VoteThreshold
)
{
proposal
.
Status
=
"DENIED"
;
}
return
proposal
;
}
Implementing Get All Proposals Function
Go to the comment
Implement Get All Proposals Logic
Create a new
ProposalList
object from the message definition in
BuildersDAO.proto
.
Fetch and iterate through
Proposals
.
Update
ProposalList
with proposal objects and return the list of proposals.
If
yesVotes
reach the threshold, update status to "PASSED".
If
noVotes
reach the threshold, update status to "DENIED".
You'll implement this function. Once done, you can proceed to the next page to compare your code with the reference implementation.
src/BuildersDAO.cs
// Implement Get All Proposals Logic
public
override
ProposalList
GetAllProposals
(
Empty
input
)
{
// Create a new list called ProposalList
// Start iterating through Proposals from index 0 until the value of NextProposalId, read the corresponding proposal, add it to ProposalList, and finally return ProposalList
// Using 'return null' to ensure the contract compiles successfully. Please update it to the correct return value when implementing
return
null
;
}
Implementing Get Proposal / Get Member Count / Get Member Exist Functions
1. Get Proposal
Navigate to
Implement Get Proposal Logic
.
Retrieve a proposal by
proposalId
.
Use
proposalId
as the key to query
State.Proposals
.
Return the corresponding proposal value.
2. Get Member Count
Navigate to
Implement Get Member Count Logic
.
Retrieve the total member count.
Return the value of
MemberCount
from
State
.
3. Get Member Exist
Navigate to
Implement Get Member Exist Logic
.
Check if a member exists by
address
.
Use
address
as the key to query
State.Members
.
Return the corresponding existence value.
Implement these methods to access different states effectively in your smart contract.
src/BuildersDAO.cs
// Implement Get Proposal Logic
public
override
Proposal
GetProposal
(
StringValue
input
)
{
var
proposal
=
State
.
Proposals
[
input
.
Value
]
;
return
proposal
;
}
// Implement Get Member Count Logic
public
override
Int32Value
GetMemberCount
(
Empty
input
)
{
var
memberCount
=
new
Int32Value
{
Value
=
State
.
MemberCount
.
Value
}
;
return
memberCount
;
}
// Implement Get Member Exist Logic
public
override
BoolValue
GetMemberExist
(
Address
input
)
{
var
exist
=
new
BoolValue
{
Value
=
State
.
Members
[
input
]
}
;
return
exist
;
}
With that, we have implemented all the functionalities of our Voting dApp smart contract.
In the next step, we will compile our smart contract and deploy our written smart contract to the aelf dAppChain.
Complete Implementation
Implementing Join DAO Function
Check Membership
: See if the address has already joined the DAO by checking State.Members. Use the Assert method for this verification.
Add New Member
: If the address isn't a member yet, add it to State.Members and set its value to true.
Update Member Count
: Increase State.MemberCount by 1 and save the new value.
src/BuildersDAO.cs
public
override
Empty
JoinDAO
(
Address
input
)
{
// Based on the address, determine whether the address has joined the DAO. If it has, throw an exception
Assert
(
!
State
.
Members
[
input
]
,
"Member is already in the DAO"
)
;
// If the address has not joined the DAO, then join and update the state's value to true
State
.
Members
[
input
]
=
true
;
// Read the value of MemberCount in the state, increment it by 1, and update it in the state
var
currentCount
=
State
.
MemberCount
.
Value
;
State
.
MemberCount
.
Value
=
currentCount
+
1
;
return
new
Empty
(
)
;
}
Implementing Get All Proposals Function
Create a list object called
ProposalList
.
Loop from 0 to the value of
State.NextProposalId
.
In each loop iteration, get the values from
State.Proposals
and add them to
ProposalList
.
Return
ProposalList
.
src/BuildersDAO.cs
public
override
ProposalList
GetAllProposals
(
Empty
input
)
{
// Create a new list called ProposalList
var
proposals
=
new
ProposalList
(
)
;
// Start iterating through Proposals from index 0 until the value of NextProposalId, read the corresponding proposal, add it to ProposalList, and finally return ProposalList
for
(
var
i
=
0
;
i
<
State
.
NextProposalId
.
Value
;
i
++
)
{
var
proposalCount
=
i
.
ToString
(
)
;
var
proposal
=
State
.
Proposals
[
proposalCount
]
;
proposals
.
Proposals
.
Add
(
proposal
)
;
}
return
proposals
;
}
Once you've implemented these two methods and run the unit tests again, you should see that all test cases pass.
Building Smart Contract
Build the new code with the following commands inside
src
folder:
Terminal
dotnet build
You should see
BuildersDAO.dll.patched
in the directory
src/bin/Debug/net.6.0
Step 3 - Deploy Smart Contract
Create A Wallet
To send transactions on the aelf blockchain, you must have a wallet.
Run this command to create aelf wallet.
Terminal
aelf-command create
You will be prompted to save your account, please do
save
your account as shown below:
Terminal
? Save account info into a file?
(
Y/n
)
Y
Make sure to choose Y to save your account information.
tip
ℹ️ Note: If you do not save your account information (by selecting n or N), do not export the wallet password. Only
proceed to the next
step if you have saved your account information.
Next, enter and confirm your password. Then export your wallet password as shown below:
Linux and macOs
Windows
Terminal
export
WALLET_PASSWORD
=
"YOUR_WALLET_PASSWORD"
Command Prompt
$env
:WALLET_PASSWORD
=
"YOUR_WALLET_PASSWORD"
Acquire Testnet Tokens (Faucet) for Development
To deploy smart contracts or execute on-chain transactions on aelf, you'll require testnet ELF tokens.
Get ELF Tokens
Go to
https://faucet-ui.aelf.dev
Enter your address and click
Get Tokens
.
Deploy Smart Contract:
The smart contract needs to be deployed on the chain before users can interact with it.
Run the following command to deploy a contract. Remember to export the path of BuildersDAO.dll.patched to CONTRACT_PATH.
Linux and macOs
Windows
Terminal
export
CONTRACT_PATH
=
$(
find
~+
.
-path
"*patched*"
|
head
-n
1
)
Terminal
aelf-deploy
-a
$WALLET_ADDRESS
-p
$WALLET_PASSWORD
-c
$CONTRACT_PATH
-e
https://tdvw-test-node.aelf.io/
Command Prompt
$CONTRACT_PATH
=
Get-ChildItem
-Recurse
-Filter
"*patched*"
|
Select-Object
-First
1
-ExpandProperty
FullName
$env
:CONTRACT_PATH
=
$CONTRACT_PATH
Command Prompt
aelf-deploy
-a
$env
:WALLET_ADDRESS
-p
$env
:WALLET_PASSWORD
-c
$env
:CONTRACT_PATH
-e
https://tdvw-test-node.aelf.io/
Please wait for approximately 1 to 2 minutes. If the deployment is successful, it will provide you with the contract address.
Copy the smart contract address from the
address
field
Export your smart contract address:
Linux and macOs
Windows
Terminal
export
CONTRACT_ADDRESS
=
"YOUR_SMART_CONTRACT_ADDRESS e.g. 2LUmicHyH4RXrMjG4beDwuDsiWJESyLkgkwPdGTR8kahRzq5XS"
Command Prompt
$env
:CONTRACT_ADDRESS
=
"YOUR_SMART_CONTRACT_ADDRESS e.g. 2LUmicHyH4RXrMjG4beDwuDsiWJESyLkgkwPdGTR8kahRzq5XS"
tip
ℹ️ Note: You are to copy the smart contract address as we will be referencing it in the next steps!
info
🎉 You have successfully deployed your dApp smart contract on the aelf testnet! In the next steps, we will be building the frontend components that allow us to interact with our deployed smart contract!
Step 4 - Interact with Your Deployed Smart Contract
Project Setup
Let's start by cloning the frontend project repository from GitHub.
Run the following command in the
capstone_aelf
directory:
Terminal
git
clone https://github.com/AElfProject/aelf-samples.git
Next, navigate to the frontend project directory with this command:
Terminal
cd
aelf-samples/vote/2-dapp
Once you're in the
2-dapp
directory, open the project with your preferred IDE (e.g., VSCode). You should see the project structure as shown below.
Install necessary libraries
Run this command in the terminal:
Terminal
npm
install
We are now ready to build the frontend components of our Voting dApp.
Configure Portkey Provider & Write Connect Wallet Function
We'll set up our Portkey provider to let users connect their Portkey wallets to our app and interact with our voting smart contract.
Go to the
src/useDAOSmartContract.ts
file.
In this file, we'll create a component that initializes the Portkey wallet provider and fetches our deployed voting smart contract. This will enable our frontend components to interact with the smart contract for actions like joining the DAO, creating proposals, and more.
Locate the comment
Step A - Setup Portkey Wallet Provider
and replace the existing
useEffect
hook with the following code snippet:
useDAOSmartContract.ts
//Step A - Setup Portkey Wallet Provider
useEffect
(
(
)
=>
{
(
async
(
)
=>
{
if
(
!
provider
)
return
null
;
try
{
// 1. get the aelf dAppChain using provider.getChain
const
chain
=
await
provider
?.
getChain
(
"tDVW"
)
;
if
(
!
chain
)
throw
new
Error
(
"No chain"
)
;
//Address of DAO Smart Contract
//Replace with Address of Deployed Smart Contract
const
address
=
"2GkJoDicXLqo7cR9YhjCEnCXQt8KUFUTPfCkeJEaAxGFYQo2tb"
;
// 2. get the DAO contract
const
daoContract
=
chain
?.
getContract
(
address
)
;
setSmartContract
(
daoContract
)
;
}
catch
(
error
)
{
console
.
log
(
error
,
"====error"
)
;
}
}
)
(
)
;
}
,
[
provider
]
)
;
tip
ℹ️ Note: You are to replace the address placeholder with your deployed voting contract address from "Deploy Voting dApp Smart Contract"!
example:
//Replace with Address of Deployed Smart Contract
const address = "your_deployed_voting_contract_address";
Next, go to the
src/HomeDAO.tsx
file.
The
HomeDAO.tsx
file is the landing page of our Voting dApp. It allows users to interact with the deployed smart contract, join the DAO, view proposals, and vote on them.
Before users can interact with the smart contract, we need to write the Connect Wallet function.
Find the comment
Step B - Connect Portkey Wallet
.
Replace the existing connect function with this code snippet:
src/HomeDAO.ts
const
connect
=
async
(
)
=>
{
//Step B - Connect Portkey Wallet
const
accounts
=
await
provider
?.
request
(
{
method
:
MethodsBase
.
REQUEST_ACCOUNTS
,
}
)
;
const
account
=
accounts
?.
tDVW
?.
[
0
]
;
setCurrentWalletAddress
(
account
)
;
setIsConnected
(
true
)
;
alert
(
"Successfully connected"
)
;
}
;
In this code, we fetch the Portkey wallet account using the provider and update the wallet address state variable. An alert notifies the user that their wallet is successfully connected.
With the Connect Wallet function defined, we're ready to write the remaining functions in the next steps.
Write Initialize Smart Contract & Join DAO Functions
Let's write the Initialize and Join DAO functions.
Find the comment
Step C - Write Initialize Smart Contract and Join DAO Logic
.
Replace the existing
initializeAndJoinDAO
function with this code snippet:
src/HomeDAO.ts
const
initializeAndJoinDAO
=
async
(
)
=>
{
//Step C - Write Initialize Smart Contract and Join DAO Logic
try
{
const
accounts
=
await
provider
?.
request
(
{
method
:
MethodsBase
.
ACCOUNTS
,
}
)
;
if
(
!
accounts
)
throw
new
Error
(
"No accounts"
)
;
const
account
=
accounts
?.
tDVW
?.
[
0
]
;
if
(
!
account
)
throw
new
Error
(
"No account"
)
;
if
(
!
initialized
)
{
await
DAOContract
?.
callSendMethod
(
"Initialize"
,
account
,
{
}
)
;
setInitialized
(
true
)
;
alert
(
"DAO Contract Successfully Initialized"
)
;
}
await
DAOContract
?.
callSendMethod
(
"JoinDAO"
,
account
,
account
)
;
setJoinedDAO
(
true
)
;
alert
(
"Successfully Joined DAO"
)
;
}
catch
(
error
)
{
console
.
error
(
error
,
"====error"
)
;
}
}
;
Here's what the function does:
Fetches your wallet account using the Portkey wallet provider.
Initializes the DAO smart contract if it hasn't been done already, updating the state and showing a success al프로젝트로 돌아가기