Awesome
On-chain Voting Tool
This repository contains a voting tool that allows for creation and facilitation of immutable, tamperproof voting using the Algorand blockchain.
Background
The Algorand Foundation aims to establish a long term, mutually beneficial relationship with the Algorand NFT ecosystem, stakeholders and community through a series of initiatives to advance the utility and support the growth of the Algorand NFT ecosystem by establishing an NFT Council. A required capability for the establishment is an on-chain voting tool to facilitate transparent community decision making. As such, this Open Source project was created to facilitate the creation of this voting tool as well as provide a general purpose on-chain voting capability to the wider ecosystem.
Components
The design for this voting tool has been documented as an Architecture Decision Record.
This project contains a number of components:
- Write-through cache Algorand IPFS gateway
- Voting dApp
- xGov dApp
- Voting smart contract
- AWS CDK Infrastructure as Code
- GitHub Actions CI/CD pipeline
Flow
Entities
Voting round creation
sequenceDiagram
Vote creator->>+dApp:Specify voting round details
Vote creator->>+dApp:Confirm
dApp->>+Wallet:Trigger signature
Wallet->>-Vote creator:Sign authorisation transaction
dApp->>IPFS:Upload voting snapshot
dApp->>-IPFS:Upload voting round metadata
dApp->>+Wallet:Trigger signature
Wallet->>-Vote creator:Sign app creation transactions
dApp->>+Wallet:Trigger signature
Wallet->>-Vote creator:Sign app bootstrap transaction
dApp-->>-Vote creator:Voting round created
Voting
sequenceDiagram
Voter->>+dApp:View voting round
dApp->>Algorand:Get app global state
dApp->>IPFS:Get voting round metadata
dApp->>IPFS:Get voting snapshot
dApp-->>-Voter:Display voting round
Voter->>dApp:Select vote options
Voter->>+dApp:Cast vote
dApp->>+Wallet:Trigger signature
Wallet->>Voter:Sign fee payment transaction
Wallet->>-Voter:Sign app call transaction
dApp->>Algorand:Submit voting transaction group
dApp-->>-Voter:Vote casted
Development setup
To run the application locally you need 3 components running:
Pre-requisites
- Install
AlgoKit
- Link; ensure you can executealgokit --version
. - Install Node.js / npm
First-time setup
- Initialise the projects, which you can do by either:
algokit bootstrap all
insrc/
, or:- Manually do it by running
npm install
and then copying.env.template
to.env
insrc/dapp
andsrc/voting-metadata-api
- Run the projects, which you can do by either:
- Opening the project in VS Code and running the
Run All
Run and Debug configuration - Run
algokit localnet start
to start a LocalNet network and runnpm run dev
insrc/dapp
andsrc/voting-metadata-api
- Opening the project in VS Code and running the
- Visit the dApp at http://localhost:5173/
After completing these steps, you should have all three components running locally, and you can test the application.
Subsequent setup
You can follow step 2 above.
Editing the smart contract
If you want to change the smart contract then you can set up the smart contract development environment by:
- Executing
algokit bootstrap all
insrc/algorand
- Editing
src/algorand/smart_contracts/voting.py
to change the smart contract andsrc/algorand/smart_contracts/deploy-config.ts
to change the local test deployment harness andsrc/algorand/smart_contracts/tests/voting.spec.ts
to change the automated tests - Build the smart contract by either executing
python -m smart_contracts build
in thesrc/algorand
folder or running the VS CodeBuild Beaker application
task - Ensure the smart contract can be deployed and executed in its happy path by either executing
npm run deploy
in thesrc/algorand/smart_contracts
folder or running the VS CodeDeploy built beaker application
Run and Debug configuration - Run the automated tests by either executing
npm run test
in thesrc/algorand/smart_contracts
folder or running tests using the VS Code Testing pane
Deployment
This solution has automated Continuous Integration and Continuous Delivery support using GitHub Actions, including Infrastructure as Code automation for AWS. This means, you can fully automate the build and multi-environment deployment of this solution without manual steps once you have configured the GitHub Actions secrets and variables.
The deployment pipeline can be found in the .github
folder.
To use it you need to configure the following with a DEV_
prefix for TestNet and PROD_
prefix for MainNet.
- Secrets
AWS_ACCESS_KEY_ID
: The access key of a "deployment user" in AWS with requisite access (see below)AWS_ACCESS_KEY_SECRET
: The secret of the deployment userCDK_DEFAULT_ACCOUNT
: The ID of the AWS account being deployed toIPFS_API_TOKEN
: An API token from a https://www.pinata.cloud/ accountALGOD_NODE_CONFIG_TOKEN
: (Optional) Token for the algod API if needed
- Variables
ALGOD_NETWORK
: The Algorand network name, eithertestnet
ormainnet
ALGOD_NODE_CONFIG_PORT
: Likely443
ALGOD_NODE_CONFIG_SERVER
: e.g.https://testnet-api.algonode.cloud/
orhttps://mainnet-api.algonode.cloud/
etc.ENVIRONMENT
:dev
orprod
INDEXER_PORT
: Likely443
INDEXER_SERVER
: e.g.https://testnet-idx.algonode.cloud/
orhttps://mainnet-idx.algonode.cloud/
etc.IPFS_GATEWAY_URL
: The URL to the IPFS gateway that is deployed as part of this solution including the/ipfs
e.g.https://api.testnet.voting.algorand.foundation/ipfs
ALGO_EXPLORER_URL
: The URL to ALGO Explorer e.g.ALGO_EXPLORER_URL
NFT_EXPLORER_URL
: The URL to NFT Explorer for an asset, minus the asset ID e.g.https://nftexplorer.app/asset/
IS_TESTNET
:true
orfalse
CREATOR_ALLOW_LIST_ADDRESSES
: The allowlist for voting round creators. To keep it open for anyone set it toany
. Alternatively you can limit access to an allowist of addresses e.g.MOIL6NTBHUFAWV5TYY6YYRJ2N3LOAPOBEV4ZPFZAJKZX3OHGQVMYLEHEUU,ODTX32FQL44D5GIJ2CMCEZ4G3FGUU3WUYDHJZDRNSSLHDO54ESGKXC25UQ
DNS
The first time you deploy to a given environment, it will reach a point in the dns-web
stack where it's waiting for the certificate validation. This is happening because it needs DNS to be delegated to the Route 53 instance that has been provisioned.
To do that you need to log into the AWS console, find the new Route 53 instance, see the NS records that are defined in it by default and add them to your source DNS. Note: this means you will be delegating that entire (sub-)domain to AWS to manage. If you don't want to do that, the alternative option is to manually create the validation CName that you will see in Route 53 and then create the other CName records that appear for the dApp and API.
Least privilege AWS access
We recommend using the following IAM policy, which gives the least access possible to the deployment user to perform the deployment of infrastructure. You just need to replace {region}
with the name of the region you are deploying into and {account}
with the ID of the account you are deploying into.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ECRPolicy",
"Effect": "Allow",
"Action": [
"ecr:CreateRepository",
"ecr:DeleteRepository",
"ecr:DescribeRepositories",
"ecr:GetLifecyclePolicy",
"ecr:GetRepositoryPolicy",
"ecr:ListTagsForResource",
"ecr:SetRepositoryPolicy",
"ecr:PutImageTagMutability",
"ecr:PutImageScanningConfiguration"
],
"Resource": [
"arn:aws:ecr:{region}:{account}:repository/*",
"arn:aws:ecr:us-east-1:{account}:repository/*"
]
},
{
"Sid": "IAMPolicy",
"Effect": "Allow",
"Action": [
"iam:AttachRolePolicy",
"iam:CreateRole",
"iam:DeleteRole",
"iam:DeleteRolePolicy",
"iam:DetachRolePolicy",
"iam:GetRole",
"iam:GetRolePolicy",
"iam:PutRolePolicy"
],
"Resource": [
"arn:aws:iam::{account}:role/*"
]
},
{
"Sid": "KMSPolicy",
"Effect": "Allow",
"Action": [
"kms:GenerateDataKey"
],
"Resource": [
"arn:aws:kms:{region}:{account}:key/*",
"arn:aws:kms:us-east-1:{account}:key/*"
]
},
{
"Sid": "S3Policy",
"Effect": "Allow",
"Action": [
"s3:CreateBucket",
"s3:DeleteBucketPolicy",
"s3:GetBucketPolicy",
"s3:PutBucketPublicAccessBlock",
"s3:PutBucketPolicy",
"s3:PutBucketVersioning",
"s3:PutEncryptionConfiguration",
"s3:ListAllMyBuckets"
],
"Resource": [
"arn:aws:s3:::*"
]
},
{
"Sid": "STSPolicy",
"Effect": "Allow",
"Action": [
"sts:AssumeRole"
],
"Resource": [
"arn:aws:iam::{account}:role/*"
]
},
{
"Sid": "SecretsManagerPolicy",
"Effect": "Allow",
"Action": [
"secretsmanager:PutSecretValue"
],
"Resource": [
"arn:aws:secretsmanager:{region}:{account}:secret:*"
]
},
{
"Sid": "SSMPolicy",
"Effect": "Allow",
"Action": [
"ssm:DeleteParameter",
"ssm:PutParameter",
"ssm:GetParameters",
"ssm:GetParameter"
],
"Resource": [
"arn:aws:ssm:{region}:{account}:parameter/*",
"arn:aws:ssm:us-east-1:{account}:parameter/*"
]
},
{
"Sid": "CloudformationPolicy",
"Effect": "Allow",
"Action": [
"cloudformation:CreateChangeSet",
"cloudformation:DescribeChangeSet",
"cloudformation:DescribeStacks",
"cloudformation:DescribeStackEvents",
"cloudformation:DeleteChangeSet",
"cloudformation:DeleteStack",
"cloudformation:ExecuteChangeSet",
"cloudformation:GetTemplate"
],
"Resource": [
"arn:aws:cloudformation:{region}:{account}:stack/*/*",
"arn:aws:cloudformation:us-east-1:{account}:stack/*/*"
]
}
]
}
Disaster Recovery
During a deployment, there is a chance for a race condition between the CDK S3 deployer and CloudFront invalidation. It is intermittent and is being tracked in an issue in the cdk library. If the deployment fails to update the Custom::CDKBucketDeployment, use the following process:
- Log into the effected AWS account and navigate to CloudFormation
- Select the failed deployment and use the
Stack options
drop down to selectContinue update rollback
- In the
Continue update rollback
, make sure to expandAdvanced
and selectskip
for the failing resource - Trigger the rollback
- In the
- Re-run the failed deployment from the CI/CD platform (GithubActions). This may require a second deployment to ensure the cache is invalidated