From c61f6c24fe4fa1f007805b329186ee0ae6b1544a Mon Sep 17 00:00:00 2001
From: Fabio Bonfiglio <fabio.bonfiglio@protonmail.ch>
Date: Mon, 17 Feb 2025 18:48:21 +0100
Subject: [PATCH 01/40] Add membership collection getter test

---
 test/organization.test.ts | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/test/organization.test.ts b/test/organization.test.ts
index 33d80a4..0f12625 100644
--- a/test/organization.test.ts
+++ b/test/organization.test.ts
@@ -61,4 +61,28 @@ describe("DAOkitOrg unit tests", () => {
 		expect(testResult.returns).toEqual(stringToHex(TEST_ORG_URI));
 		expect(hexToString(testResult.returns)).toEqual(TEST_ORG_URI);
 	});
+
+	it("Should return correct Membership Collection ID", async () => {
+		const testResult = await DAOkitOrg.tests.getACcollectionId({
+			address: addressFromContractId(testOrgId),
+			initialAsset: {
+				alphAmount: MINIMAL_CONTRACT_DEPOSIT,
+				tokens: []
+			},
+			initialFields: {
+				proposalTemplateId: testProposalTemplate,
+				ballotTemplateId: testBallotTemplate,
+				organizationUri:  stringToHex(TEST_ORG_URI),
+				membershipCollectionId: testENFTcertificatorId,
+				minimalCommitmentPeriod: uintToByteVec(604800000, 8),
+				maximalCommitmentPeriod: uintToByteVec(2419200000, 8),
+				minimalVotingPeriod: uintToByteVec(172800000, 8),
+				commitmentDepositAmount: 0n,
+				commitmentDepositTokenId: ALPH_TOKEN_ID,
+				requiredQuorums: testQuorums,
+				currentProposalIndex: 0n
+			}
+		});
+		expect(testResult.returns).toEqual(testENFTcertificatorId);
+	});
 });
-- 
GitLab


From 4decd8ca57aa4932465c8957245a5e3e6bac1edd Mon Sep 17 00:00:00 2001
From: Fabio Bonfiglio <fabio.bonfiglio@protonmail.ch>
Date: Mon, 17 Feb 2025 19:07:12 +0100
Subject: [PATCH 02/40] Add tests for governance rules and proposal index
 getters

---
 test/organization.test.ts | 69 +++++++++++++++++++++++++++++++++++----
 1 file changed, 63 insertions(+), 6 deletions(-)

diff --git a/test/organization.test.ts b/test/organization.test.ts
index 0f12625..ba2bd68 100644
--- a/test/organization.test.ts
+++ b/test/organization.test.ts
@@ -32,6 +32,9 @@ describe("DAOkitOrg unit tests", () => {
 	const testOrgId = randomContractId();
 	const owner = testAddress;
 	const testQuorums = uintToByteVec(200, 2).concat(uintToByteVec(250, 2).repeat(2), uintToByteVec(420, 2).repeat(2));
+	const MIN_COMMIT_PERIOD = 604800000;
+	const MAX_COMMIT_PERIOD = 2419200000;
+	const MIN_VOTING_PERIOD = 172800000;
 
 	beforeAll(() => {
 		web3.setCurrentNodeProvider(`http://${process.env.CI ? "devnet" : "localhost"}:22973`, undefined, fetch);
@@ -49,9 +52,9 @@ describe("DAOkitOrg unit tests", () => {
 				ballotTemplateId: testBallotTemplate,
 				organizationUri:  stringToHex(TEST_ORG_URI),
 				membershipCollectionId: testENFTcertificatorId,
-				minimalCommitmentPeriod: uintToByteVec(604800000, 8),
-				maximalCommitmentPeriod: uintToByteVec(2419200000, 8),
-				minimalVotingPeriod: uintToByteVec(172800000, 8),
+				minimalCommitmentPeriod: uintToByteVec(MIN_COMMIT_PERIOD, 8),
+				maximalCommitmentPeriod: uintToByteVec(MAX_COMMIT_PERIOD, 8),
+				minimalVotingPeriod: uintToByteVec(MIN_VOTING_PERIOD, 8),
 				commitmentDepositAmount: 0n,
 				commitmentDepositTokenId: ALPH_TOKEN_ID,
 				requiredQuorums: testQuorums,
@@ -74,9 +77,9 @@ describe("DAOkitOrg unit tests", () => {
 				ballotTemplateId: testBallotTemplate,
 				organizationUri:  stringToHex(TEST_ORG_URI),
 				membershipCollectionId: testENFTcertificatorId,
-				minimalCommitmentPeriod: uintToByteVec(604800000, 8),
-				maximalCommitmentPeriod: uintToByteVec(2419200000, 8),
-				minimalVotingPeriod: uintToByteVec(172800000, 8),
+				minimalCommitmentPeriod: uintToByteVec(MIN_COMMIT_PERIOD, 8),
+				maximalCommitmentPeriod: uintToByteVec(MAX_COMMIT_PERIOD, 8),
+				minimalVotingPeriod: uintToByteVec(MIN_VOTING_PERIOD, 8),
 				commitmentDepositAmount: 0n,
 				commitmentDepositTokenId: ALPH_TOKEN_ID,
 				requiredQuorums: testQuorums,
@@ -85,4 +88,58 @@ describe("DAOkitOrg unit tests", () => {
 		});
 		expect(testResult.returns).toEqual(testENFTcertificatorId);
 	});
+
+	it("Should return correct governance rules", async () => {
+		const testResult = await DAOkitOrg.tests.getGovernanceRules({
+			address: addressFromContractId(testOrgId),
+			initialAsset: {
+				alphAmount: MINIMAL_CONTRACT_DEPOSIT,
+				tokens: []
+			},
+			initialFields: {
+				proposalTemplateId: testProposalTemplate,
+				ballotTemplateId: testBallotTemplate,
+				organizationUri:  stringToHex(TEST_ORG_URI),
+				membershipCollectionId: testENFTcertificatorId,
+				minimalCommitmentPeriod: uintToByteVec(MIN_COMMIT_PERIOD, 8),
+				maximalCommitmentPeriod: uintToByteVec(MAX_COMMIT_PERIOD, 8),
+				minimalVotingPeriod: uintToByteVec(MIN_VOTING_PERIOD, 8),
+				commitmentDepositAmount: 42n,
+				commitmentDepositTokenId: ALPH_TOKEN_ID,
+				requiredQuorums: testQuorums,
+				currentProposalIndex: 0n
+			}
+		});
+		expect(testResult.returns[0]).toEqual(testENFTcertificatorId);
+		expect(testResult.returns[1]).toEqual(BigInt(MIN_COMMIT_PERIOD));
+		expect(testResult.returns[2]).toEqual(BigInt(MAX_COMMIT_PERIOD));
+		expect(testResult.returns[3]).toEqual(BigInt(MIN_VOTING_PERIOD));
+		expect(testResult.returns[4]).toEqual(42n);
+		expect(testResult.returns[5]).toEqual(ALPH_TOKEN_ID);
+		expect(testResult.returns[6]).toEqual(testQuorums);
+	});
+
+	it("Should return latest proposal index", async () => {
+		const testResult = await DAOkitOrg.tests.latestProposal({
+			address: addressFromContractId(testOrgId),
+			initialAsset: {
+				alphAmount: MINIMAL_CONTRACT_DEPOSIT,
+				tokens: []
+			},
+			initialFields: {
+				proposalTemplateId: testProposalTemplate,
+				ballotTemplateId: testBallotTemplate,
+				organizationUri:  stringToHex(TEST_ORG_URI),
+				membershipCollectionId: testENFTcertificatorId,
+				minimalCommitmentPeriod: uintToByteVec(MIN_COMMIT_PERIOD, 8),
+				maximalCommitmentPeriod: uintToByteVec(MAX_COMMIT_PERIOD, 8),
+				minimalVotingPeriod: uintToByteVec(MIN_VOTING_PERIOD, 8),
+				commitmentDepositAmount: 0n,
+				commitmentDepositTokenId: ALPH_TOKEN_ID,
+				requiredQuorums: testQuorums,
+				currentProposalIndex: 42n
+			}
+		});
+		expect(testResult.returns).toEqual(42n);
+	});
 });
-- 
GitLab


From 8611543efd345ff702ccc3b9ed6d1c3d8a97b13b Mon Sep 17 00:00:00 2001
From: Fabio Bonfiglio <fabio.bonfiglio@protonmail.ch>
Date: Mon, 17 Feb 2025 21:41:08 +0100
Subject: [PATCH 03/40] Add internal method setOrganizationUri test

---
 test/organization.test.ts | 28 ++++++++++++++++++++++++++++
 1 file changed, 28 insertions(+)

diff --git a/test/organization.test.ts b/test/organization.test.ts
index ba2bd68..d0095c5 100644
--- a/test/organization.test.ts
+++ b/test/organization.test.ts
@@ -142,4 +142,32 @@ describe("DAOkitOrg unit tests", () => {
 		});
 		expect(testResult.returns).toEqual(42n);
 	});
+
+	it("Internal method should modify org's URI when passed correct payload", async () => {
+		const NEW_URI = "https://example.com/newOrgFrontend";
+		const testResult = await DAOkitOrg.tests.setOrganizationUri({
+			address: addressFromContractId(testOrgId),
+			initialAsset: {
+				alphAmount: MINIMAL_CONTRACT_DEPOSIT,
+				tokens: []
+			},
+			initialFields: {
+				proposalTemplateId: testProposalTemplate,
+				ballotTemplateId: testBallotTemplate,
+				organizationUri:  stringToHex(TEST_ORG_URI),
+				membershipCollectionId: testENFTcertificatorId,
+				minimalCommitmentPeriod: uintToByteVec(MIN_COMMIT_PERIOD, 8),
+				maximalCommitmentPeriod: uintToByteVec(MAX_COMMIT_PERIOD, 8),
+				minimalVotingPeriod: uintToByteVec(MIN_VOTING_PERIOD, 8),
+				commitmentDepositAmount: 0n,
+				commitmentDepositTokenId: ALPH_TOKEN_ID,
+				requiredQuorums: testQuorums,
+				currentProposalIndex: 42n
+			},
+			testArgs: {
+				payload: uintToByteVec(4, 1).concat(uintToByteVec(NEW_URI.length, 2), stringToHex(NEW_URI), stringToHex("https://b0c0c1600d4bb0c0c1600d4b.example.com/metadata.json"))
+			}
+		});
+		expect(hexToString(testResult.contracts[0].fields.organizationUri.toString())).toEqual(NEW_URI);
+	});
 });
-- 
GitLab


From 1ec7e12a5c7c260bb57d8e61c1a8ea8f4a51bfa9 Mon Sep 17 00:00:00 2001
From: Fabio Bonfiglio <fabio.bonfiglio@protonmail.ch>
Date: Mon, 17 Feb 2025 21:41:24 +0100
Subject: [PATCH 04/40] Correct payload slicing

---
 .project.json                | 4 ++--
 artifacts/DAOkitOrg.ral.json | 4 ++--
 artifacts/ts/DAOkitOrg.ts    | 2 +-
 contracts/organization.ral   | 2 +-
 4 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/.project.json b/.project.json
index 623db77..2c67475 100644
--- a/.project.json
+++ b/.project.json
@@ -13,9 +13,9 @@
   "infos": {
     "DAOkitOrg": {
       "sourceFile": "organization.ral",
-      "sourceCodeHash": "4d028ad863c6fab9cf3209a909fb5d15d460206ae88620d1f8c8d4c6c866204d",
+      "sourceCodeHash": "f48b4f952baac99cc2427811c6c478e1e9a307692f5016d979d1159133f2fec0",
       "bytecodeDebugPatch": "",
-      "codeHashDebug": "3b400c13c8e16e20c597383e0ca8cc415ccabf21095771d09c6abeefe2478bde"
+      "codeHashDebug": "a8d798aef21df4d49fa83e6c3007c0e547dc5dc9ff4574e30f1e07dd90f0ce6d"
     },
     "DAOkitProposal": {
       "sourceFile": "proposal.ral",
diff --git a/artifacts/DAOkitOrg.ral.json b/artifacts/DAOkitOrg.ral.json
index 5c60a19..1512114 100644
--- a/artifacts/DAOkitOrg.ral.json
+++ b/artifacts/DAOkitOrg.ral.json
@@ -1,8 +1,8 @@
 {
   "version": "v3.11.1",
   "name": "DAOkitOrg",
-  "bytecode": "0b0c0e1c403940474062408a414c416d419241ea421742bf010000000103d3facdcc89a00002010000000103d3926e1fc1a0010201000000070cd3fa5d1fcba001a0026fa0036fa0046fa005a006a00702010000000103d3d22165c1a0080201000102010bd3ecf235d9160040cb17011601c51341947b160102010001030112d30fec8d94160017010c0e160101011817021602a001410c0d1601d4fbe73eea1a020103040c01406ad33ad9a30a160000051341917ba0080d2a17041602a0036f344c0556a0036f2a4a0d1602a0026f324c0556a0026f2a4a035616022a17051603a0046f324c051605a0046f2a4a03160516032a170616010c0d626c17070d16070d2b0e2c2a1708b41600ce01b1a006a0051604160516061404ffffffffa007160816080e2a62130b641601140101140480000000140400000001140400000001130564170a1709b47ad1a216000da3160440ce001609160ac1170b1604a108160b0201000203000fd3ed1a13f8160040cb17021602c51341947bb416010e0c1602010700000101001614010416000c0d62411341907b16000f0f16000d0e626d2a62a10006a0005e0000010100402c14010516000c0d62411341907b16000d132162a10116001321132962a10216001329133162a10316001331133962a104160013391340596271a105160013405913407962a106160013407913408362a10700020101001714010316000c0d62411341907b16000d13216263160013211340416216001340411340616271ab01000004004049d307d5527db317000c0e1600d46590af8f17011816010004b3411341937b0c0d1600d4d0148c55140107411341907b0c0d1600d41de3aebe170216020c0d6217031603140102414c06140454686973140c544f20494d504c454d454e54411341a27b4a1a1603140103414c031602000a4a131603140104414c03160200084a0c1603140105414c03160200094a051603140101411341907b0c0c1600d4b573a9b4",
-  "codeHash": "3b400c13c8e16e20c597383e0ca8cc415ccabf21095771d09c6abeefe2478bde",
+  "bytecode": "0b0c0e1c403940474062408a414c416d419241ea421742bf010000000103d3facdcc89a00002010000000103d3926e1fc1a0010201000000070cd3fa5d1fcba001a0026fa0036fa0046fa005a006a00702010000000103d3d22165c1a0080201000102010bd3ecf235d9160040cb17011601c51341947b160102010001030112d30fec8d94160017010c0e160101011817021602a001410c0d1601d4fbe73eea1a020103040c01406ad33ad9a30a160000051341917ba0080d2a17041602a0036f344c0556a0036f2a4a0d1602a0026f324c0556a0026f2a4a035616022a17051603a0046f324c051605a0046f2a4a03160516032a170616010c0d626c17070d16070d2b0e2c2a1708b41600ce01b1a006a0051604160516061404ffffffffa007160816080e2a62130b641601140101140480000000140400000001140400000001130564170a1709b47ad1a216000da3160440ce001609160ac1170b1604a108160b0201000203000fd3ed1a13f8160040cb17021602c51341947bb416010e0c1602010700000101001614010416000c0d62411341907b16000f0f16000d0f626d2a62a10006a0005e0000010100402c14010516000c0d62411341907b16000d132162a10116001321132962a10216001329133162a10316001331133962a104160013391340596271a105160013405913407962a106160013407913408362a10700020101001714010316000c0d62411341907b16000d13216263160013211340416216001340411340616271ab01000004004049d307d5527db317000c0e1600d46590af8f17011816010004b3411341937b0c0d1600d4d0148c55140107411341907b0c0d1600d41de3aebe170216020c0d6217031603140102414c06140454686973140c544f20494d504c454d454e54411341a27b4a1a1603140103414c031602000a4a131603140104414c03160200084a0c1603140105414c03160200094a051603140101411341907b0c0c1600d4b573a9b4",
+  "codeHash": "a8d798aef21df4d49fa83e6c3007c0e547dc5dc9ff4574e30f1e07dd90f0ce6d",
   "fieldsSig": {
     "names": [
       "proposalTemplateId",
diff --git a/artifacts/ts/DAOkitOrg.ts b/artifacts/ts/DAOkitOrg.ts
index 978e2d0..c9b5e40 100644
--- a/artifacts/ts/DAOkitOrg.ts
+++ b/artifacts/ts/DAOkitOrg.ts
@@ -378,7 +378,7 @@ export const DAOkitOrg = new Factory(
   Contract.fromJson(
     DAOkitOrgContractJson,
     "",
-    "3b400c13c8e16e20c597383e0ca8cc415ccabf21095771d09c6abeefe2478bde",
+    "a8d798aef21df4d49fa83e6c3007c0e547dc5dc9ff4574e30f1e07dd90f0ce6d",
     []
   )
 );
diff --git a/contracts/organization.ral b/contracts/organization.ral
index a188122..0621ae5 100644
--- a/contracts/organization.ral
+++ b/contracts/organization.ral
@@ -120,7 +120,7 @@ Contract DAOkitOrg(
 	fn setOrganizationUri(payload: ByteVec) -> () {
 		assert!(ProposalTypes.URI == byteVecSlice!(payload, 0u, 1u), ErrorCodes.BadRequest)
 		organizationUri = byteVecSlice!(
-			payload, 3u, 3u + u256From2Byte!(byteVecSlice!(payload, 1u, 2u)))
+			payload, 3u, 3u + u256From2Byte!(byteVecSlice!(payload, 1u, 3u)))
 		emit OrgUriUpdated(organizationUri)
 	}
 
-- 
GitLab


From b6d0e41a3e96d04cc57093adab0c2f78534a8252 Mon Sep 17 00:00:00 2001
From: Fabio Bonfiglio <fabio.bonfiglio@protonmail.ch>
Date: Tue, 18 Feb 2025 17:33:17 +0100
Subject: [PATCH 05/40] Re-use event to signal Org's changes

---
 .project.json                |  4 ++--
 artifacts/DAOkitOrg.ral.json |  6 +++---
 artifacts/ts/DAOkitOrg.ts    | 14 +++++++-------
 contracts/organization.ral   |  5 +++--
 4 files changed, 15 insertions(+), 14 deletions(-)

diff --git a/.project.json b/.project.json
index 2c67475..8d0f9aa 100644
--- a/.project.json
+++ b/.project.json
@@ -13,9 +13,9 @@
   "infos": {
     "DAOkitOrg": {
       "sourceFile": "organization.ral",
-      "sourceCodeHash": "f48b4f952baac99cc2427811c6c478e1e9a307692f5016d979d1159133f2fec0",
+      "sourceCodeHash": "126af6bd5dfd237add34f74570662ec0d66dc3509d129005b4018482c5e5ade7",
       "bytecodeDebugPatch": "",
-      "codeHashDebug": "a8d798aef21df4d49fa83e6c3007c0e547dc5dc9ff4574e30f1e07dd90f0ce6d"
+      "codeHashDebug": "984e7ae99078f331725141c809e837e1076058a928adb4f9983d8655fdb68fb9"
     },
     "DAOkitProposal": {
       "sourceFile": "proposal.ral",
diff --git a/artifacts/DAOkitOrg.ral.json b/artifacts/DAOkitOrg.ral.json
index 1512114..f21de99 100644
--- a/artifacts/DAOkitOrg.ral.json
+++ b/artifacts/DAOkitOrg.ral.json
@@ -1,8 +1,8 @@
 {
   "version": "v3.11.1",
   "name": "DAOkitOrg",
-  "bytecode": "0b0c0e1c403940474062408a414c416d419241ea421742bf010000000103d3facdcc89a00002010000000103d3926e1fc1a0010201000000070cd3fa5d1fcba001a0026fa0036fa0046fa005a006a00702010000000103d3d22165c1a0080201000102010bd3ecf235d9160040cb17011601c51341947b160102010001030112d30fec8d94160017010c0e160101011817021602a001410c0d1601d4fbe73eea1a020103040c01406ad33ad9a30a160000051341917ba0080d2a17041602a0036f344c0556a0036f2a4a0d1602a0026f324c0556a0026f2a4a035616022a17051603a0046f324c051605a0046f2a4a03160516032a170616010c0d626c17070d16070d2b0e2c2a1708b41600ce01b1a006a0051604160516061404ffffffffa007160816080e2a62130b641601140101140480000000140400000001140400000001130564170a1709b47ad1a216000da3160440ce001609160ac1170b1604a108160b0201000203000fd3ed1a13f8160040cb17021602c51341947bb416010e0c1602010700000101001614010416000c0d62411341907b16000f0f16000d0f626d2a62a10006a0005e0000010100402c14010516000c0d62411341907b16000d132162a10116001321132962a10216001329133162a10316001331133962a104160013391340596271a105160013405913407962a106160013407913408362a10700020101001714010316000c0d62411341907b16000d13216263160013211340416216001340411340616271ab01000004004049d307d5527db317000c0e1600d46590af8f17011816010004b3411341937b0c0d1600d4d0148c55140107411341907b0c0d1600d41de3aebe170216020c0d6217031603140102414c06140454686973140c544f20494d504c454d454e54411341a27b4a1a1603140103414c031602000a4a131603140104414c03160200084a0c1603140105414c03160200094a051603140101411341907b0c0c1600d4b573a9b4",
-  "codeHash": "a8d798aef21df4d49fa83e6c3007c0e547dc5dc9ff4574e30f1e07dd90f0ce6d",
+  "bytecode": "0b0c0e1c403940474062408a414c416d419241ee421b42c3010000000103d3facdcc89a00002010000000103d3926e1fc1a0010201000000070cd3fa5d1fcba001a0026fa0036fa0046fa005a006a00702010000000103d3d22165c1a0080201000102010bd3ecf235d9160040cb17011601c51341947b160102010001030112d30fec8d94160017010c0e160101011817021602a001410c0d1601d4fbe73eea1a020103040c01406ad33ad9a30a160000051341917ba0080d2a17041602a0036f344c0556a0036f2a4a0d1602a0026f324c0556a0026f2a4a035616022a17051603a0046f324c051605a0046f2a4a03160516032a170616010c0d626c17070d16070d2b0e2c2a1708b41600ce01b1a006a0051604160516061404ffffffffa007160816080e2a62130b641601140101140480000000140400000001140400000001130564170a1709b47ad1a216000da3160440ce001609160ac1170b1604a108160b0201000203000fd3ed1a13f8160040cb17021602c51341947bb416010e0c1602010700000101001614010416000c0d62411341907b16000f0f16000d0f626d2a62a10006a0005e0000010100402f14010516000c0d62411341907b16000d132162a10116001321132962a10216001329133162a10316001331133962a104160013391340596271a105160013405913407962a106160013407913408362a10706a0005e00020101001714010316000c0d62411341907b16000d13216263160013211340416216001340411340616271ab01000004004049d307d5527db317000c0e1600d46590af8f17011816010004b3411341937b0c0d1600d4d0148c55140107411341907b0c0d1600d41de3aebe170216020c0d6217031603140102414c06140454686973140c544f20494d504c454d454e54411341a27b4a1a1603140103414c031602000a4a131603140104414c03160200084a0c1603140105414c03160200094a051603140101411341907b0c0c1600d4b573a9b4",
+  "codeHash": "984e7ae99078f331725141c809e837e1076058a928adb4f9983d8655fdb68fb9",
   "fieldsSig": {
     "names": [
       "proposalTemplateId",
@@ -55,7 +55,7 @@
       ]
     },
     {
-      "name": "OrgUriUpdated",
+      "name": "OrgUpdated",
       "fieldNames": [
         "uri"
       ],
diff --git a/artifacts/ts/DAOkitOrg.ts b/artifacts/ts/DAOkitOrg.ts
index c9b5e40..0bb562e 100644
--- a/artifacts/ts/DAOkitOrg.ts
+++ b/artifacts/ts/DAOkitOrg.ts
@@ -55,7 +55,7 @@ export namespace DAOkitOrgTypes {
   export type State = ContractState<Fields>;
 
   export type MetadataUpdatedEvent = ContractEvent<{ uri: HexString }>;
-  export type OrgUriUpdatedEvent = ContractEvent<{ uri: HexString }>;
+  export type OrgUpdatedEvent = ContractEvent<{ uri: HexString }>;
   export type NewProposalEvent = ContractEvent<{ proposalId: HexString }>;
 
   export interface CallMethodTable {
@@ -210,7 +210,7 @@ class Factory extends ContractFactory<
     );
   }
 
-  eventIndex = { MetadataUpdated: 0, OrgUriUpdated: 1, NewProposal: 2 };
+  eventIndex = { MetadataUpdated: 0, OrgUpdated: 1, NewProposal: 2 };
   consts = {
     ErrorCodes: {
       BadRequest: BigInt("400"),
@@ -378,7 +378,7 @@ export const DAOkitOrg = new Factory(
   Contract.fromJson(
     DAOkitOrgContractJson,
     "",
-    "a8d798aef21df4d49fa83e6c3007c0e547dc5dc9ff4574e30f1e07dd90f0ce6d",
+    "984e7ae99078f331725141c809e837e1076058a928adb4f9983d8655fdb68fb9",
     []
   )
 );
@@ -411,15 +411,15 @@ export class DAOkitOrgInstance extends ContractInstance {
     );
   }
 
-  subscribeOrgUriUpdatedEvent(
-    options: EventSubscribeOptions<DAOkitOrgTypes.OrgUriUpdatedEvent>,
+  subscribeOrgUpdatedEvent(
+    options: EventSubscribeOptions<DAOkitOrgTypes.OrgUpdatedEvent>,
     fromCount?: number
   ): EventSubscription {
     return subscribeContractEvent(
       DAOkitOrg.contract,
       this,
       options,
-      "OrgUriUpdated",
+      "OrgUpdated",
       fromCount
     );
   }
@@ -440,7 +440,7 @@ export class DAOkitOrgInstance extends ContractInstance {
   subscribeAllEvents(
     options: EventSubscribeOptions<
       | DAOkitOrgTypes.MetadataUpdatedEvent
-      | DAOkitOrgTypes.OrgUriUpdatedEvent
+      | DAOkitOrgTypes.OrgUpdatedEvent
       | DAOkitOrgTypes.NewProposalEvent
     >,
     fromCount?: number
diff --git a/contracts/organization.ral b/contracts/organization.ral
index 0621ae5..f805d3a 100644
--- a/contracts/organization.ral
+++ b/contracts/organization.ral
@@ -12,7 +12,7 @@ Contract DAOkitOrg(
 	mut currentProposalIndex: U256
 ) implements IDAOkitOrg {
 
-	event OrgUriUpdated(uri: ByteVec)
+	event OrgUpdated(uri: ByteVec)
 	event NewProposal(proposalId: ByteVec)
 
 	enum ErrorCodes {
@@ -121,7 +121,7 @@ Contract DAOkitOrg(
 		assert!(ProposalTypes.URI == byteVecSlice!(payload, 0u, 1u), ErrorCodes.BadRequest)
 		organizationUri = byteVecSlice!(
 			payload, 3u, 3u + u256From2Byte!(byteVecSlice!(payload, 1u, 3u)))
-		emit OrgUriUpdated(organizationUri)
+		emit OrgUpdated(organizationUri)
 	}
 
 	@using(updateFields = true)
@@ -134,6 +134,7 @@ Contract DAOkitOrg(
 		commitmentDepositAmount = u256From32Byte!(byteVecSlice!(payload, 57u, 89u))
 		commitmentDepositTokenId = byteVecSlice!(payload, 89u, 121u)
 		requiredQuorums = byteVecSlice!(payload, 121u, 131u)
+		emit OrgUpdated(organizationUri)
 	}
 
 	@using(assetsInContract = true)
-- 
GitLab


From 4bb58928a4f17d35c1f339579921f74332dc941d Mon Sep 17 00:00:00 2001
From: Fabio Bonfiglio <fabio.bonfiglio@protonmail.ch>
Date: Tue, 18 Feb 2025 17:33:59 +0100
Subject: [PATCH 06/40] More realistic amount

---
 test/organization.test.ts | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/test/organization.test.ts b/test/organization.test.ts
index d0095c5..460b690 100644
--- a/test/organization.test.ts
+++ b/test/organization.test.ts
@@ -104,7 +104,7 @@ describe("DAOkitOrg unit tests", () => {
 				minimalCommitmentPeriod: uintToByteVec(MIN_COMMIT_PERIOD, 8),
 				maximalCommitmentPeriod: uintToByteVec(MAX_COMMIT_PERIOD, 8),
 				minimalVotingPeriod: uintToByteVec(MIN_VOTING_PERIOD, 8),
-				commitmentDepositAmount: 42n,
+				commitmentDepositAmount: 42n * 10n**18n,
 				commitmentDepositTokenId: ALPH_TOKEN_ID,
 				requiredQuorums: testQuorums,
 				currentProposalIndex: 0n
@@ -114,7 +114,7 @@ describe("DAOkitOrg unit tests", () => {
 		expect(testResult.returns[1]).toEqual(BigInt(MIN_COMMIT_PERIOD));
 		expect(testResult.returns[2]).toEqual(BigInt(MAX_COMMIT_PERIOD));
 		expect(testResult.returns[3]).toEqual(BigInt(MIN_VOTING_PERIOD));
-		expect(testResult.returns[4]).toEqual(42n);
+		expect(testResult.returns[4]).toEqual(42n * 10n**18n);
 		expect(testResult.returns[5]).toEqual(ALPH_TOKEN_ID);
 		expect(testResult.returns[6]).toEqual(testQuorums);
 	});
-- 
GitLab


From a028b8712f05ce389132738f340a0e1365a314d1 Mon Sep 17 00:00:00 2001
From: Fabio Bonfiglio <fabio.bonfiglio@protonmail.ch>
Date: Tue, 18 Feb 2025 17:34:17 +0100
Subject: [PATCH 07/40] Semantic

---
 test/organization.test.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/organization.test.ts b/test/organization.test.ts
index 460b690..2492025 100644
--- a/test/organization.test.ts
+++ b/test/organization.test.ts
@@ -143,7 +143,7 @@ describe("DAOkitOrg unit tests", () => {
 		expect(testResult.returns).toEqual(42n);
 	});
 
-	it("Internal method should modify org's URI when passed correct payload", async () => {
+	it("Private method should modify org's URI when passed correct payload", async () => {
 		const NEW_URI = "https://example.com/newOrgFrontend";
 		const testResult = await DAOkitOrg.tests.setOrganizationUri({
 			address: addressFromContractId(testOrgId),
-- 
GitLab


From 50555bc9d03984bf7272bf85fa2cccc175d119e8 Mon Sep 17 00:00:00 2001
From: Fabio Bonfiglio <fabio.bonfiglio@protonmail.ch>
Date: Tue, 18 Feb 2025 18:57:27 +0100
Subject: [PATCH 08/40] Add `byteVecToUint` helper method

---
 test/organization.test.ts | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/test/organization.test.ts b/test/organization.test.ts
index 2492025..a4d6c3e 100644
--- a/test/organization.test.ts
+++ b/test/organization.test.ts
@@ -1,5 +1,5 @@
 /* eslint-disable @typescript-eslint/no-unused-vars */
-import { addressFromContractId, ALPH_TOKEN_ID, binToHex, hexToString, MINIMAL_CONTRACT_DEPOSIT, stringToHex, web3 } from "@alephium/web3";
+import { addressFromContractId, ALPH_TOKEN_ID, binToHex, hexToString, MINIMAL_CONTRACT_DEPOSIT, stringToHex, Val, web3 } from "@alephium/web3";
 import { randomContractId, testAddress } from "@alephium/web3-test";
 import { DAOkitOrg } from "../artifacts/ts";
 
@@ -25,6 +25,15 @@ function uintToByteVec(value: bigint | number, bytes: number): string {
 	return byteVecStr;
 }
 
+/**
+ * Helper function to convert a returned ByteVec string into a BigInt.
+ * @param byteVec The ByteVec returned from a contract call
+ * @returns The value as a BigInt
+ */
+function byteVecToUint(byteVec: Val): bigint {
+	return BigInt("0x" + byteVec.toString());
+}
+
 describe("DAOkitOrg unit tests", () => {
 	const testProposalTemplate = randomContractId();
 	const testBallotTemplate = randomContractId();
-- 
GitLab


From 9466ab0f06156ef5c8616b48fce56065ec678516 Mon Sep 17 00:00:00 2001
From: Fabio Bonfiglio <fabio.bonfiglio@protonmail.ch>
Date: Tue, 18 Feb 2025 18:58:09 +0100
Subject: [PATCH 09/40] Add private methods tests

---
 test/organization.test.ts | 91 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 91 insertions(+)

diff --git a/test/organization.test.ts b/test/organization.test.ts
index a4d6c3e..0ae4632 100644
--- a/test/organization.test.ts
+++ b/test/organization.test.ts
@@ -178,5 +178,96 @@ describe("DAOkitOrg unit tests", () => {
 			}
 		});
 		expect(hexToString(testResult.contracts[0].fields.organizationUri.toString())).toEqual(NEW_URI);
+		expect(testResult.events.length).toEqual(1);
+		expect(testResult.events[0].name).toEqual("OrgUpdated");
+		expect(hexToString(testResult.events[0].fields.uri.toString())).toEqual(NEW_URI);
 	});
+
+	it("Private method should reject new URI when passed incorrect payload", async () => {
+		const NEW_URI = "https://example.com/newOrgFrontend";
+		const testResult = await DAOkitOrg.tests.setOrganizationUri({
+			address: addressFromContractId(testOrgId),
+			initialAsset: {
+				alphAmount: MINIMAL_CONTRACT_DEPOSIT,
+				tokens: []
+			},
+			initialFields: {
+				proposalTemplateId: testProposalTemplate,
+				ballotTemplateId: testBallotTemplate,
+				organizationUri:  stringToHex(TEST_ORG_URI),
+				membershipCollectionId: testENFTcertificatorId,
+				minimalCommitmentPeriod: uintToByteVec(MIN_COMMIT_PERIOD, 8),
+				maximalCommitmentPeriod: uintToByteVec(MAX_COMMIT_PERIOD, 8),
+				minimalVotingPeriod: uintToByteVec(MIN_VOTING_PERIOD, 8),
+				commitmentDepositAmount: 0n,
+				commitmentDepositTokenId: ALPH_TOKEN_ID,
+				requiredQuorums: testQuorums,
+				currentProposalIndex: 42n
+			},
+			testArgs: {
+				payload: uintToByteVec(5, 1).concat(uintToByteVec(NEW_URI.length, 2), stringToHex(NEW_URI), stringToHex("https://b0c0c1600d4bb0c0c1600d4b.example.com/metadata.json"))
+			}
+		}).catch(err => {
+			expect(err.message).toMatch(`Assertion Failed in Contract @ ${addressFromContractId(testOrgId)}, Error Code: 400`);
+		});
+	});
+
+	it("Private method should replace governance rules when passed correct payload", async () => {
+		const NEW_MEMBERSHIP_COLL_ID = randomContractId();
+		const NEW_MIN_COMMIT_PERIOD = 302400000;
+		const NEW_MAX_COMMIT_PERIOD = 1209600000;
+		const NEW_MIN_VOTING_PERIOD = 86400000n;
+		const NEW_DEPOSIT_AMOUNT = 42n * 10n**18n;
+		const NEW_DEPOSIT_TOKENID = randomContractId();
+		const NEW_QUORUMS = uintToByteVec(120, 2).concat(
+			uintToByteVec(220, 2),
+			uintToByteVec(200, 2),
+			uintToByteVec(300, 2),
+			uintToByteVec(420, 2)
+		);
+		const testResult = await DAOkitOrg.tests.setGovernanceRules({
+			address: addressFromContractId(testOrgId),
+			initialAsset: {
+				alphAmount: MINIMAL_CONTRACT_DEPOSIT,
+				tokens: []
+			},
+			initialFields: {
+				proposalTemplateId: testProposalTemplate,
+				ballotTemplateId: testBallotTemplate,
+				organizationUri:  stringToHex(TEST_ORG_URI),
+				membershipCollectionId: testENFTcertificatorId,
+				minimalCommitmentPeriod: uintToByteVec(MIN_COMMIT_PERIOD, 8),
+				maximalCommitmentPeriod: uintToByteVec(MAX_COMMIT_PERIOD, 8),
+				minimalVotingPeriod: uintToByteVec(MIN_VOTING_PERIOD, 8),
+				commitmentDepositAmount: 0n,
+				commitmentDepositTokenId: ALPH_TOKEN_ID,
+				requiredQuorums: testQuorums,
+				currentProposalIndex: 42n
+			},
+			testArgs: {
+				payload: uintToByteVec(5, 1).concat(
+					NEW_MEMBERSHIP_COLL_ID,
+					uintToByteVec(NEW_MIN_COMMIT_PERIOD, 8),
+					uintToByteVec(NEW_MAX_COMMIT_PERIOD, 8),
+					uintToByteVec(NEW_MIN_VOTING_PERIOD, 8),
+					uintToByteVec(NEW_DEPOSIT_AMOUNT, 32),
+					NEW_DEPOSIT_TOKENID,
+					NEW_QUORUMS,
+					stringToHex("https://b0c0c1600d4bb0c0c1600d4b.example.com/metadata.json")
+				)
+			}
+		});
+		expect(testResult.contracts[0].fields.membershipCollectionId).toEqual(NEW_MEMBERSHIP_COLL_ID);
+		expect(byteVecToUint(testResult.contracts[0].fields.minimalCommitmentPeriod)).toEqual(BigInt(NEW_MIN_COMMIT_PERIOD));
+		expect(byteVecToUint(testResult.contracts[0].fields.maximalCommitmentPeriod)).toEqual(BigInt(NEW_MAX_COMMIT_PERIOD));
+		expect(byteVecToUint(testResult.contracts[0].fields.minimalVotingPeriod)).toEqual(NEW_MIN_VOTING_PERIOD);
+		expect(testResult.contracts[0].fields.commitmentDepositAmount).toEqual(NEW_DEPOSIT_AMOUNT);
+		expect(testResult.contracts[0].fields.commitmentDepositTokenId).toEqual(NEW_DEPOSIT_TOKENID);
+		expect(testResult.contracts[0].fields.requiredQuorums).toEqual(NEW_QUORUMS);
+		expect(testResult.events.length).toEqual(1);
+		expect(testResult.events[0].name).toEqual("OrgUpdated");
+		expect(hexToString(testResult.events[0].fields.uri.toString())).toEqual(TEST_ORG_URI);
+	});
+
+	it.todo("Private method should reject when trying to change governance with incorrect payload type");
 });
-- 
GitLab


From 6f1f5fa7a408fc89dba1476697e9cf4192d40b66 Mon Sep 17 00:00:00 2001
From: Fabio Bonfiglio <fabio.bonfiglio@protonmail.ch>
Date: Wed, 19 Feb 2025 14:53:44 +0100
Subject: [PATCH 10/40] Add rejection test when trying to change gov rules with
 wrong payload

---
 test/organization.test.ts | 51 ++++++++++++++++++++++++++++++++++++++-
 1 file changed, 50 insertions(+), 1 deletion(-)

diff --git a/test/organization.test.ts b/test/organization.test.ts
index 0ae4632..078b854 100644
--- a/test/organization.test.ts
+++ b/test/organization.test.ts
@@ -269,5 +269,54 @@ describe("DAOkitOrg unit tests", () => {
 		expect(hexToString(testResult.events[0].fields.uri.toString())).toEqual(TEST_ORG_URI);
 	});
 
-	it.todo("Private method should reject when trying to change governance with incorrect payload type");
+	it("Private method should reject when trying to change governance with incorrect payload type", async () => {
+		const NEW_MEMBERSHIP_COLL_ID = randomContractId();
+		const NEW_MIN_COMMIT_PERIOD = 302400000;
+		const NEW_MAX_COMMIT_PERIOD = 1209600000;
+		const NEW_MIN_VOTING_PERIOD = 86400000n;
+		const NEW_DEPOSIT_AMOUNT = 42n * 10n**18n;
+		const NEW_DEPOSIT_TOKENID = randomContractId();
+		const NEW_QUORUMS = uintToByteVec(120, 2).concat(
+			uintToByteVec(220, 2),
+			uintToByteVec(200, 2),
+			uintToByteVec(300, 2),
+			uintToByteVec(420, 2)
+		);
+		try {
+			const testResult = await DAOkitOrg.tests.setGovernanceRules({
+				address: addressFromContractId(testOrgId),
+				initialAsset: {
+					alphAmount: MINIMAL_CONTRACT_DEPOSIT,
+					tokens: []
+				},
+				initialFields: {
+					proposalTemplateId: testProposalTemplate,
+					ballotTemplateId: testBallotTemplate,
+					organizationUri:  stringToHex(TEST_ORG_URI),
+					membershipCollectionId: testENFTcertificatorId,
+					minimalCommitmentPeriod: uintToByteVec(MIN_COMMIT_PERIOD, 8),
+					maximalCommitmentPeriod: uintToByteVec(MAX_COMMIT_PERIOD, 8),
+					minimalVotingPeriod: uintToByteVec(MIN_VOTING_PERIOD, 8),
+					commitmentDepositAmount: 0n,
+					commitmentDepositTokenId: ALPH_TOKEN_ID,
+					requiredQuorums: testQuorums,
+					currentProposalIndex: 42n
+				},
+				testArgs: {
+					payload: uintToByteVec(6, 1).concat(
+						NEW_MEMBERSHIP_COLL_ID,
+						uintToByteVec(NEW_MIN_COMMIT_PERIOD, 8),
+						uintToByteVec(NEW_MAX_COMMIT_PERIOD, 8),
+						uintToByteVec(NEW_MIN_VOTING_PERIOD, 8),
+						uintToByteVec(NEW_DEPOSIT_AMOUNT, 32),
+						NEW_DEPOSIT_TOKENID,
+						NEW_QUORUMS,
+						stringToHex("https://b0c0c1600d4bb0c0c1600d4b.example.com/metadata.json")
+					)
+				}
+			});
+		} catch (err) {
+			expect(err.message).toMatch(`Assertion Failed in Contract @ ${addressFromContractId(testOrgId)}, Error Code: 400`);
+		}
+	});
 });
-- 
GitLab


From c42eb32c6b1703d07469b8dbe9e5273eae1fab99 Mon Sep 17 00:00:00 2001
From: Fabio Bonfiglio <fabio.bonfiglio@protonmail.ch>
Date: Wed, 19 Feb 2025 14:58:52 +0100
Subject: [PATCH 11/40] Add todos

---
 test/organization.test.ts | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/test/organization.test.ts b/test/organization.test.ts
index 078b854..dd9b322 100644
--- a/test/organization.test.ts
+++ b/test/organization.test.ts
@@ -319,4 +319,8 @@ describe("DAOkitOrg unit tests", () => {
 			expect(err.message).toMatch(`Assertion Failed in Contract @ ${addressFromContractId(testOrgId)}, Error Code: 400`);
 		}
 	});
+
+	it.todo("Private transfer method should transfer correct asset when passed correct payload.");
+	it.todo("Private transfer method should reject when passed incorrect payload");
+	it.todo("Private transfer method should fail when insufficient amount of the desired token");
 });
-- 
GitLab


From 33b36fcc73c37f644c068bbe909f8d835bfd4611 Mon Sep 17 00:00:00 2001
From: Fabio Bonfiglio <fabio.bonfiglio@protonmail.ch>
Date: Thu, 20 Feb 2025 17:20:11 +0100
Subject: [PATCH 12/40] Correct hex address size

---
 .project.json                | 4 ++--
 artifacts/DAOkitOrg.ral.json | 4 ++--
 artifacts/ts/DAOkitOrg.ts    | 2 +-
 contracts/organization.ral   | 6 +++---
 4 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/.project.json b/.project.json
index 8d0f9aa..8478fd3 100644
--- a/.project.json
+++ b/.project.json
@@ -13,9 +13,9 @@
   "infos": {
     "DAOkitOrg": {
       "sourceFile": "organization.ral",
-      "sourceCodeHash": "126af6bd5dfd237add34f74570662ec0d66dc3509d129005b4018482c5e5ade7",
+      "sourceCodeHash": "d4c80245e5f2e51a89e05cdb73a3b8a561f3a2f07ac3a4a8735fbdae5256bcc8",
       "bytecodeDebugPatch": "",
-      "codeHashDebug": "984e7ae99078f331725141c809e837e1076058a928adb4f9983d8655fdb68fb9"
+      "codeHashDebug": "360a5758e1ac8886962c287041626b8c02bbdb7408e8f7bc4c72d6fa37f69314"
     },
     "DAOkitProposal": {
       "sourceFile": "proposal.ral",
diff --git a/artifacts/DAOkitOrg.ral.json b/artifacts/DAOkitOrg.ral.json
index f21de99..9606d21 100644
--- a/artifacts/DAOkitOrg.ral.json
+++ b/artifacts/DAOkitOrg.ral.json
@@ -1,8 +1,8 @@
 {
   "version": "v3.11.1",
   "name": "DAOkitOrg",
-  "bytecode": "0b0c0e1c403940474062408a414c416d419241ee421b42c3010000000103d3facdcc89a00002010000000103d3926e1fc1a0010201000000070cd3fa5d1fcba001a0026fa0036fa0046fa005a006a00702010000000103d3d22165c1a0080201000102010bd3ecf235d9160040cb17011601c51341947b160102010001030112d30fec8d94160017010c0e160101011817021602a001410c0d1601d4fbe73eea1a020103040c01406ad33ad9a30a160000051341917ba0080d2a17041602a0036f344c0556a0036f2a4a0d1602a0026f324c0556a0026f2a4a035616022a17051603a0046f324c051605a0046f2a4a03160516032a170616010c0d626c17070d16070d2b0e2c2a1708b41600ce01b1a006a0051604160516061404ffffffffa007160816080e2a62130b641601140101140480000000140400000001140400000001130564170a1709b47ad1a216000da3160440ce001609160ac1170b1604a108160b0201000203000fd3ed1a13f8160040cb17021602c51341947bb416010e0c1602010700000101001614010416000c0d62411341907b16000f0f16000d0f626d2a62a10006a0005e0000010100402f14010516000c0d62411341907b16000d132162a10116001321132962a10216001329133162a10316001331133962a104160013391340596271a105160013405913407962a106160013407913408362a10706a0005e00020101001714010316000c0d62411341907b16000d13216263160013211340416216001340411340616271ab01000004004049d307d5527db317000c0e1600d46590af8f17011816010004b3411341937b0c0d1600d4d0148c55140107411341907b0c0d1600d41de3aebe170216020c0d6217031603140102414c06140454686973140c544f20494d504c454d454e54411341a27b4a1a1603140103414c031602000a4a131603140104414c03160200084a0c1603140105414c03160200094a051603140101411341907b0c0c1600d4b573a9b4",
-  "codeHash": "984e7ae99078f331725141c809e837e1076058a928adb4f9983d8655fdb68fb9",
+  "bytecode": "0b0c0e1c403940474062408a414c416d419241ee421b42c3010000000103d3facdcc89a00002010000000103d3926e1fc1a0010201000000070cd3fa5d1fcba001a0026fa0036fa0046fa005a006a00702010000000103d3d22165c1a0080201000102010bd3ecf235d9160040cb17011601c51341947b160102010001030112d30fec8d94160017010c0e160101011817021602a001410c0d1601d4fbe73eea1a020103040c01406ad33ad9a30a160000051341917ba0080d2a17041602a0036f344c0556a0036f2a4a0d1602a0026f324c0556a0026f2a4a035616022a17051603a0046f324c051605a0046f2a4a03160516032a170616010c0d626c17070d16070d2b0e2c2a1708b41600ce01b1a006a0051604160516061404ffffffffa007160816080e2a62130b641601140101140480000000140400000001140400000001130564170a1709b47ad1a216000da3160440ce001609160ac1170b1604a108160b0201000203000fd3ed1a13f8160040cb17021602c51341947bb416010e0c1602010700000101001614010416000c0d62411341907b16000f0f16000d0f626d2a62a10006a0005e0000010100402f14010516000c0d62411341907b16000d132162a10116001321132962a10216001329133162a10316001331133962a104160013391340596271a105160013405913407962a106160013407913408362a10706a0005e00020101001714010316000c0d62411341907b16000d132e62631600132e13404e62160013404e13406e6271ab01000004004049d307d5527db317000c0e1600d46590af8f17011816010004b3411341937b0c0d1600d4d0148c55140107411341907b0c0d1600d41de3aebe170216020c0d6217031603140102414c06140454686973140c544f20494d504c454d454e54411341a27b4a1a1603140103414c031602000a4a131603140104414c03160200084a0c1603140105414c03160200094a051603140101411341907b0c0c1600d4b573a9b4",
+  "codeHash": "360a5758e1ac8886962c287041626b8c02bbdb7408e8f7bc4c72d6fa37f69314",
   "fieldsSig": {
     "names": [
       "proposalTemplateId",
diff --git a/artifacts/ts/DAOkitOrg.ts b/artifacts/ts/DAOkitOrg.ts
index 0bb562e..ced0be8 100644
--- a/artifacts/ts/DAOkitOrg.ts
+++ b/artifacts/ts/DAOkitOrg.ts
@@ -378,7 +378,7 @@ export const DAOkitOrg = new Factory(
   Contract.fromJson(
     DAOkitOrgContractJson,
     "",
-    "984e7ae99078f331725141c809e837e1076058a928adb4f9983d8655fdb68fb9",
+    "360a5758e1ac8886962c287041626b8c02bbdb7408e8f7bc4c72d6fa37f69314",
     []
   )
 );
diff --git a/contracts/organization.ral b/contracts/organization.ral
index f805d3a..d097e8e 100644
--- a/contracts/organization.ral
+++ b/contracts/organization.ral
@@ -141,9 +141,9 @@ Contract DAOkitOrg(
 	fn transfer(payload: ByteVec) -> () {
 		assert!(ProposalTypes.Transfer == byteVecSlice!(payload, 0u, 1u), ErrorCodes.BadRequest)
 		transferTokenFromSelf!(
-			byteVecToAddress!(byteVecSlice!(payload, 1u, 33u)),
-			byteVecSlice!(payload, 33u, 65u),
-			u256From32Byte!(byteVecSlice!(payload, 65u, 97u)))
+			byteVecToAddress!(byteVecSlice!(payload, 1u, 46u)),
+			byteVecSlice!(payload, 46u, 78u),
+			u256From32Byte!(byteVecSlice!(payload, 78u, 110u)))
 	}
 
 	pub fn enact() -> () {
-- 
GitLab


From deba9c3a3cd73f493bf4b38bc1a1e94745a54aca Mon Sep 17 00:00:00 2001
From: Fabio Bonfiglio <fabio.bonfiglio@protonmail.ch>
Date: Thu, 20 Feb 2025 17:21:55 +0100
Subject: [PATCH 13/40] Add private transfer method test (WIP)

---
 test/organization.test.ts | 46 ++++++++++++++++++++++++++++++++++++---
 1 file changed, 43 insertions(+), 3 deletions(-)

diff --git a/test/organization.test.ts b/test/organization.test.ts
index dd9b322..2a0260a 100644
--- a/test/organization.test.ts
+++ b/test/organization.test.ts
@@ -1,5 +1,5 @@
 /* eslint-disable @typescript-eslint/no-unused-vars */
-import { addressFromContractId, ALPH_TOKEN_ID, binToHex, hexToString, MINIMAL_CONTRACT_DEPOSIT, stringToHex, Val, web3 } from "@alephium/web3";
+import { Address, addressFromContractId, ALPH_TOKEN_ID, binToHex, hexToString, MINIMAL_CONTRACT_DEPOSIT, stringToHex, Val, web3 } from "@alephium/web3";
 import { randomContractId, testAddress } from "@alephium/web3-test";
 import { DAOkitOrg } from "../artifacts/ts";
 
@@ -39,7 +39,6 @@ describe("DAOkitOrg unit tests", () => {
 	const testBallotTemplate = randomContractId();
 	const testENFTcertificatorId = randomContractId();
 	const testOrgId = randomContractId();
-	const owner = testAddress;
 	const testQuorums = uintToByteVec(200, 2).concat(uintToByteVec(250, 2).repeat(2), uintToByteVec(420, 2).repeat(2));
 	const MIN_COMMIT_PERIOD = 604800000;
 	const MAX_COMMIT_PERIOD = 2419200000;
@@ -47,6 +46,8 @@ describe("DAOkitOrg unit tests", () => {
 
 	beforeAll(() => {
 		web3.setCurrentNodeProvider(`http://${process.env.CI ? "devnet" : "localhost"}:22973`, undefined, fetch);
+		console.debug(`Tests running on ${process.env.CI ? "CI devnet service" : "locally running devnet"}`);
+		console.debug("Test address (used as recipient, etc):", testAddress);
 	});
 
 	it("Should return correct Organization URI", async () => {
@@ -320,7 +321,46 @@ describe("DAOkitOrg unit tests", () => {
 		}
 	});
 
-	it.todo("Private transfer method should transfer correct asset when passed correct payload.");
+	it.skip("Private transfer method should transfer correct asset when passed correct payload.", async () => {
+		const TEST_TOKEN_ID = randomContractId();
+		const TEST_AMOUNT = 42n * 10n**18n;
+		const TEST_RECIPIENT: Address = testAddress;
+		console.debug("TestAddress length:", testAddress.length);
+		const testResult = await DAOkitOrg.tests.transfer({
+			initialAsset: {
+				alphAmount: MINIMAL_CONTRACT_DEPOSIT,
+				tokens: [
+					{
+						id: TEST_TOKEN_ID,
+						amount: TEST_AMOUNT
+					}
+				]
+			},
+			initialFields: {
+				proposalTemplateId: testProposalTemplate,
+				ballotTemplateId: testBallotTemplate,
+				organizationUri:  stringToHex(TEST_ORG_URI),
+				membershipCollectionId: testENFTcertificatorId,
+				minimalCommitmentPeriod: uintToByteVec(MIN_COMMIT_PERIOD, 8),
+				maximalCommitmentPeriod: uintToByteVec(MAX_COMMIT_PERIOD, 8),
+				minimalVotingPeriod: uintToByteVec(MIN_VOTING_PERIOD, 8),
+				commitmentDepositAmount: 0n,
+				commitmentDepositTokenId: ALPH_TOKEN_ID,
+				requiredQuorums: testQuorums,
+				currentProposalIndex: 42n
+			},
+			testArgs: {
+				payload: uintToByteVec(3, 1).concat(
+					stringToHex(TEST_RECIPIENT),
+					TEST_TOKEN_ID,
+					uintToByteVec(TEST_AMOUNT, 32),
+					stringToHex("https://b0c0c1600d4bb0c0c1600d4b.example.com/metadata.json")
+				)
+			}
+		});
+		expect(testResult).toEqual({});
+	});
+
 	it.todo("Private transfer method should reject when passed incorrect payload");
 	it.todo("Private transfer method should fail when insufficient amount of the desired token");
 });
-- 
GitLab


From 08b175e714348d7b09d4e3026459f3ce9a6dca40 Mon Sep 17 00:00:00 2001
From: Fabio Bonfiglio <fabio.bonfiglio@protonmail.ch>
Date: Fri, 21 Feb 2025 17:47:57 +0100
Subject: [PATCH 14/40] Add dust ALPH amount when transferring tokens

---
 .project.json                |  4 ++--
 artifacts/DAOkitOrg.ral.json |  4 ++--
 artifacts/ts/DAOkitOrg.ts    |  2 +-
 contracts/organization.ral   | 15 ++++++++++++---
 4 files changed, 17 insertions(+), 8 deletions(-)

diff --git a/.project.json b/.project.json
index 8478fd3..c1d3603 100644
--- a/.project.json
+++ b/.project.json
@@ -13,9 +13,9 @@
   "infos": {
     "DAOkitOrg": {
       "sourceFile": "organization.ral",
-      "sourceCodeHash": "d4c80245e5f2e51a89e05cdb73a3b8a561f3a2f07ac3a4a8735fbdae5256bcc8",
+      "sourceCodeHash": "9723be23a1f1daf999215626c6616adbb311198e49f54e5850ae1ccf90de135f",
       "bytecodeDebugPatch": "",
-      "codeHashDebug": "360a5758e1ac8886962c287041626b8c02bbdb7408e8f7bc4c72d6fa37f69314"
+      "codeHashDebug": "a3fc9de272220d02f20a1e7158c869acaf75c14f4db645c9a3d73721700debf3"
     },
     "DAOkitProposal": {
       "sourceFile": "proposal.ral",
diff --git a/artifacts/DAOkitOrg.ral.json b/artifacts/DAOkitOrg.ral.json
index 9606d21..60cbc71 100644
--- a/artifacts/DAOkitOrg.ral.json
+++ b/artifacts/DAOkitOrg.ral.json
@@ -1,8 +1,8 @@
 {
   "version": "v3.11.1",
   "name": "DAOkitOrg",
-  "bytecode": "0b0c0e1c403940474062408a414c416d419241ee421b42c3010000000103d3facdcc89a00002010000000103d3926e1fc1a0010201000000070cd3fa5d1fcba001a0026fa0036fa0046fa005a006a00702010000000103d3d22165c1a0080201000102010bd3ecf235d9160040cb17011601c51341947b160102010001030112d30fec8d94160017010c0e160101011817021602a001410c0d1601d4fbe73eea1a020103040c01406ad33ad9a30a160000051341917ba0080d2a17041602a0036f344c0556a0036f2a4a0d1602a0026f324c0556a0026f2a4a035616022a17051603a0046f324c051605a0046f2a4a03160516032a170616010c0d626c17070d16070d2b0e2c2a1708b41600ce01b1a006a0051604160516061404ffffffffa007160816080e2a62130b641601140101140480000000140400000001140400000001130564170a1709b47ad1a216000da3160440ce001609160ac1170b1604a108160b0201000203000fd3ed1a13f8160040cb17021602c51341947bb416010e0c1602010700000101001614010416000c0d62411341907b16000f0f16000d0f626d2a62a10006a0005e0000010100402f14010516000c0d62411341907b16000d132162a10116001321132962a10216001329133162a10316001331133962a104160013391340596271a105160013405913407962a106160013407913408362a10706a0005e00020101001714010316000c0d62411341907b16000d132e62631600132e13404e62160013404e13406e6271ab01000004004049d307d5527db317000c0e1600d46590af8f17011816010004b3411341937b0c0d1600d4d0148c55140107411341907b0c0d1600d41de3aebe170216020c0d6217031603140102414c06140454686973140c544f20494d504c454d454e54411341a27b4a1a1603140103414c031602000a4a131603140104414c03160200084a0c1603140105414c03160200094a051603140101411341907b0c0c1600d4b573a9b4",
-  "codeHash": "360a5758e1ac8886962c287041626b8c02bbdb7408e8f7bc4c72d6fa37f69314",
+  "bytecode": "0b0c0e1c403940474062408a414c416d419241ee423b42e3010000000103d3facdcc89a00002010000000103d3926e1fc1a0010201000000070cd3fa5d1fcba001a0026fa0036fa0046fa005a006a00702010000000103d3d22165c1a0080201000102010bd3ecf235d9160040cb17011601c51341947b160102010001030112d30fec8d94160017010c0e160101011817021602a001410c0d1601d4fbe73eea1a020103040c01406ad33ad9a30a160000051341917ba0080d2a17041602a0036f344c0556a0036f2a4a0d1602a0026f324c0556a0026f2a4a035616022a17051603a0046f324c051605a0046f2a4a03160516032a170616010c0d626c17070d16070d2b0e2c2a1708b41600ce01b1a006a0051604160516061404ffffffffa007160816080e2a62130b641601140101140480000000140400000001140400000001130564170a1709b47ad1a216000da3160440ce001609160ac1170b1604a108160b0201000203000fd3ed1a13f8160040cb17021602c51341947bb416010e0c1602010700000101001614010416000c0d62411341907b16000f0f16000d0f626d2a62a10006a0005e0000010100402f14010516000c0d62411341907b16000d132162a10116001321132962a10216001329133162a10316001331133962a104160013391340596271a105160013405913407962a106160013407913408362a10706a0005e0002010300402514010316000c0d62411341907b16000d132262631701160013221340426217021602132065424c05160113206513c3038d7ea4c68000ab1601160216001340421340626271ab01000004004049d307d5527db317000c0e1600d46590af8f17011816010004b3411341937b0c0d1600d4d0148c55140107411341907b0c0d1600d41de3aebe170216020c0d6217031603140102414c06140454686973140c544f20494d504c454d454e54411341a27b4a1a1603140103414c031602000a4a131603140104414c03160200084a0c1603140105414c03160200094a051603140101411341907b0c0c1600d4b573a9b4",
+  "codeHash": "a3fc9de272220d02f20a1e7158c869acaf75c14f4db645c9a3d73721700debf3",
   "fieldsSig": {
     "names": [
       "proposalTemplateId",
diff --git a/artifacts/ts/DAOkitOrg.ts b/artifacts/ts/DAOkitOrg.ts
index ced0be8..ca4b182 100644
--- a/artifacts/ts/DAOkitOrg.ts
+++ b/artifacts/ts/DAOkitOrg.ts
@@ -378,7 +378,7 @@ export const DAOkitOrg = new Factory(
   Contract.fromJson(
     DAOkitOrgContractJson,
     "",
-    "360a5758e1ac8886962c287041626b8c02bbdb7408e8f7bc4c72d6fa37f69314",
+    "a3fc9de272220d02f20a1e7158c869acaf75c14f4db645c9a3d73721700debf3",
     []
   )
 );
diff --git a/contracts/organization.ral b/contracts/organization.ral
index d097e8e..55f57bd 100644
--- a/contracts/organization.ral
+++ b/contracts/organization.ral
@@ -140,10 +140,19 @@ Contract DAOkitOrg(
 	@using(assetsInContract = true)
 	fn transfer(payload: ByteVec) -> () {
 		assert!(ProposalTypes.Transfer == byteVecSlice!(payload, 0u, 1u), ErrorCodes.BadRequest)
+		let recipient = byteVecToAddress!(byteVecSlice!(payload, 1u, 34u))
+		let tokenId = byteVecSlice!(payload, 34u, 66u)
+		if (tokenId != zeros!(32u)) {
+			transferTokenFromSelf!(
+				recipient,
+				zeros!(32u),
+				dustAmount!()
+			)
+		}
 		transferTokenFromSelf!(
-			byteVecToAddress!(byteVecSlice!(payload, 1u, 46u)),
-			byteVecSlice!(payload, 46u, 78u),
-			u256From32Byte!(byteVecSlice!(payload, 78u, 110u)))
+			recipient,
+			tokenId,
+			u256From32Byte!(byteVecSlice!(payload, 66u, 98u)))
 	}
 
 	pub fn enact() -> () {
-- 
GitLab


From 6018bb4eccf26fff755cdcd9613877be09e7b451 Mon Sep 17 00:00:00 2001
From: Fabio Bonfiglio <fabio.bonfiglio@protonmail.ch>
Date: Fri, 21 Feb 2025 17:50:00 +0100
Subject: [PATCH 15/40] Correct encoding of Address in a ByteVec

---
 test/organization.test.ts | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/test/organization.test.ts b/test/organization.test.ts
index 2a0260a..b26e391 100644
--- a/test/organization.test.ts
+++ b/test/organization.test.ts
@@ -1,5 +1,5 @@
 /* eslint-disable @typescript-eslint/no-unused-vars */
-import { Address, addressFromContractId, ALPH_TOKEN_ID, binToHex, hexToString, MINIMAL_CONTRACT_DEPOSIT, stringToHex, Val, web3 } from "@alephium/web3";
+import { Address, addressFromContractId, ALPH_TOKEN_ID, binToHex, bs58, DUST_AMOUNT, hexToString, MINIMAL_CONTRACT_DEPOSIT, stringToHex, Val, web3 } from "@alephium/web3";
 import { randomContractId, testAddress } from "@alephium/web3-test";
 import { DAOkitOrg } from "../artifacts/ts";
 
@@ -321,14 +321,14 @@ describe("DAOkitOrg unit tests", () => {
 		}
 	});
 
-	it.skip("Private transfer method should transfer correct asset when passed correct payload.", async () => {
+	it("Private transfer method should transfer correct asset when passed correct payload.", async () => {
 		const TEST_TOKEN_ID = randomContractId();
 		const TEST_AMOUNT = 42n * 10n**18n;
 		const TEST_RECIPIENT: Address = testAddress;
 		console.debug("TestAddress length:", testAddress.length);
 		const testResult = await DAOkitOrg.tests.transfer({
 			initialAsset: {
-				alphAmount: MINIMAL_CONTRACT_DEPOSIT,
+				alphAmount: MINIMAL_CONTRACT_DEPOSIT + DUST_AMOUNT,
 				tokens: [
 					{
 						id: TEST_TOKEN_ID,
@@ -351,14 +351,15 @@ describe("DAOkitOrg unit tests", () => {
 			},
 			testArgs: {
 				payload: uintToByteVec(3, 1).concat(
-					stringToHex(TEST_RECIPIENT),
+					binToHex(bs58.decode(TEST_RECIPIENT)),
 					TEST_TOKEN_ID,
 					uintToByteVec(TEST_AMOUNT, 32),
 					stringToHex("https://b0c0c1600d4bb0c0c1600d4b.example.com/metadata.json")
 				)
 			}
 		});
-		expect(testResult).toEqual({});
+		expect(testResult.contracts[0].asset.alphAmount).toEqual(MINIMAL_CONTRACT_DEPOSIT);
+		expect(testResult.contracts[0].asset.tokens).toEqual([]);
 	});
 
 	it.todo("Private transfer method should reject when passed incorrect payload");
-- 
GitLab


From 797dd02c4ae3a41290675ed09739537309a2f74a Mon Sep 17 00:00:00 2001
From: Fabio Bonfiglio <fabio.bonfiglio@protonmail.ch>
Date: Fri, 21 Feb 2025 18:25:15 +0100
Subject: [PATCH 16/40] Add ALPH and insufficient amounts transfer tests

---
 test/organization.test.ts | 130 +++++++++++++++++++++++++++++++++++++-
 1 file changed, 127 insertions(+), 3 deletions(-)

diff --git a/test/organization.test.ts b/test/organization.test.ts
index b26e391..b167b91 100644
--- a/test/organization.test.ts
+++ b/test/organization.test.ts
@@ -325,8 +325,8 @@ describe("DAOkitOrg unit tests", () => {
 		const TEST_TOKEN_ID = randomContractId();
 		const TEST_AMOUNT = 42n * 10n**18n;
 		const TEST_RECIPIENT: Address = testAddress;
-		console.debug("TestAddress length:", testAddress.length);
 		const testResult = await DAOkitOrg.tests.transfer({
+			address: addressFromContractId(testOrgId),
 			initialAsset: {
 				alphAmount: MINIMAL_CONTRACT_DEPOSIT + DUST_AMOUNT,
 				tokens: [
@@ -362,6 +362,130 @@ describe("DAOkitOrg unit tests", () => {
 		expect(testResult.contracts[0].asset.tokens).toEqual([]);
 	});
 
-	it.todo("Private transfer method should reject when passed incorrect payload");
-	it.todo("Private transfer method should fail when insufficient amount of the desired token");
+	it("Private transfer method should transfer ALPH correctly.", async () => {
+		const TEST_TOKEN_ID = randomContractId();
+		const TEST_AMOUNT = 42n * 10n**18n;
+		const TEST_RECIPIENT: Address = testAddress;
+		const testResult = await DAOkitOrg.tests.transfer({
+			address: addressFromContractId(testOrgId),
+			initialAsset: {
+				alphAmount: MINIMAL_CONTRACT_DEPOSIT + DUST_AMOUNT + TEST_AMOUNT,
+				tokens: [
+					{
+						id: TEST_TOKEN_ID,
+						amount: TEST_AMOUNT
+					}
+				]
+			},
+			initialFields: {
+				proposalTemplateId: testProposalTemplate,
+				ballotTemplateId: testBallotTemplate,
+				organizationUri:  stringToHex(TEST_ORG_URI),
+				membershipCollectionId: testENFTcertificatorId,
+				minimalCommitmentPeriod: uintToByteVec(MIN_COMMIT_PERIOD, 8),
+				maximalCommitmentPeriod: uintToByteVec(MAX_COMMIT_PERIOD, 8),
+				minimalVotingPeriod: uintToByteVec(MIN_VOTING_PERIOD, 8),
+				commitmentDepositAmount: 0n,
+				commitmentDepositTokenId: ALPH_TOKEN_ID,
+				requiredQuorums: testQuorums,
+				currentProposalIndex: 42n
+			},
+			testArgs: {
+				payload: uintToByteVec(3, 1).concat(
+					binToHex(bs58.decode(TEST_RECIPIENT)),
+					ALPH_TOKEN_ID,
+					uintToByteVec(TEST_AMOUNT, 32),
+					stringToHex("https://b0c0c1600d4bb0c0c1600d4b.example.com/metadata.json")
+				)
+			}
+		});
+		expect(testResult.contracts[0].asset.alphAmount).toEqual(MINIMAL_CONTRACT_DEPOSIT + DUST_AMOUNT);
+		expect(testResult.contracts[0].asset.tokens).toEqual([{ id: TEST_TOKEN_ID, amount: TEST_AMOUNT }]);
+	});
+
+	it("Private transfer method should reject when passed incorrect payload", async () => {
+		const TEST_TOKEN_ID = randomContractId();
+		const TEST_AMOUNT = 42n * 10n**18n;
+		const TEST_RECIPIENT: Address = testAddress;
+		try {
+			const testResult = await DAOkitOrg.tests.transfer({
+				address: addressFromContractId(testOrgId),
+				initialAsset: {
+					alphAmount: MINIMAL_CONTRACT_DEPOSIT + DUST_AMOUNT,
+					tokens: [
+						{
+							id: TEST_TOKEN_ID,
+							amount: TEST_AMOUNT
+						}
+					]
+				},
+				initialFields: {
+					proposalTemplateId: testProposalTemplate,
+					ballotTemplateId: testBallotTemplate,
+					organizationUri:  stringToHex(TEST_ORG_URI),
+					membershipCollectionId: testENFTcertificatorId,
+					minimalCommitmentPeriod: uintToByteVec(MIN_COMMIT_PERIOD, 8),
+					maximalCommitmentPeriod: uintToByteVec(MAX_COMMIT_PERIOD, 8),
+					minimalVotingPeriod: uintToByteVec(MIN_VOTING_PERIOD, 8),
+					commitmentDepositAmount: 0n,
+					commitmentDepositTokenId: ALPH_TOKEN_ID,
+					requiredQuorums: testQuorums,
+					currentProposalIndex: 42n
+				},
+				testArgs: {
+					payload: uintToByteVec(2, 1).concat(
+						binToHex(bs58.decode(TEST_RECIPIENT)),
+						TEST_TOKEN_ID,
+						uintToByteVec(TEST_AMOUNT, 32),
+						stringToHex("https://b0c0c1600d4bb0c0c1600d4b.example.com/metadata.json")
+					)
+				}
+			});
+		} catch (err) {
+			expect(err.message).toMatch(`VM execution error: Assertion Failed in Contract @ ${addressFromContractId(testOrgId)}, Error Code: 400`);
+		}
+	});
+
+	it("Private transfer method should fail when insufficient amount of the desired token", async () => {
+		const TEST_TOKEN_ID = randomContractId();
+		const TEST_AMOUNT = 42n * 10n**18n;
+		const TEST_RECIPIENT: Address = testAddress;
+		try {
+			const testResult = await DAOkitOrg.tests.transfer({
+				address: addressFromContractId(testOrgId),
+				initialAsset: {
+					alphAmount: MINIMAL_CONTRACT_DEPOSIT + DUST_AMOUNT,
+					tokens: [
+						{
+							id: TEST_TOKEN_ID,
+							amount: TEST_AMOUNT
+						}
+					]
+				},
+				initialFields: {
+					proposalTemplateId: testProposalTemplate,
+					ballotTemplateId: testBallotTemplate,
+					organizationUri:  stringToHex(TEST_ORG_URI),
+					membershipCollectionId: testENFTcertificatorId,
+					minimalCommitmentPeriod: uintToByteVec(MIN_COMMIT_PERIOD, 8),
+					maximalCommitmentPeriod: uintToByteVec(MAX_COMMIT_PERIOD, 8),
+					minimalVotingPeriod: uintToByteVec(MIN_VOTING_PERIOD, 8),
+					commitmentDepositAmount: 0n,
+					commitmentDepositTokenId: ALPH_TOKEN_ID,
+					requiredQuorums: testQuorums,
+					currentProposalIndex: 42n
+				},
+				testArgs: {
+					payload: uintToByteVec(3, 1).concat(
+						binToHex(bs58.decode(TEST_RECIPIENT)),
+						TEST_TOKEN_ID,
+						uintToByteVec(TEST_AMOUNT * 2n, 32),
+						stringToHex("https://b0c0c1600d4bb0c0c1600d4b.example.com/metadata.json")
+					)
+				}
+			});
+		} catch (err) {	
+			expect(err.message).toMatch(`Not enough approved balance for address ${addressFromContractId(testOrgId)}, tokenId: ${TEST_TOKEN_ID}, expected: ${BigInt(TEST_AMOUNT * 2n).toString()}, got: ${BigInt(TEST_AMOUNT).toString()}`);
+		}
+	});
 });
-- 
GitLab


From 947a08cace21bcb808e2578ffd3c99a2ef7f8bc1 Mon Sep 17 00:00:00 2001
From: Fabio Bonfiglio <fabio.bonfiglio@protonmail.ch>
Date: Fri, 21 Feb 2025 18:30:03 +0100
Subject: [PATCH 17/40] Remove no-unused-var rule cancellation

---
 test/organization.test.ts | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/test/organization.test.ts b/test/organization.test.ts
index b167b91..c34266a 100644
--- a/test/organization.test.ts
+++ b/test/organization.test.ts
@@ -1,4 +1,3 @@
-/* eslint-disable @typescript-eslint/no-unused-vars */
 import { Address, addressFromContractId, ALPH_TOKEN_ID, binToHex, bs58, DUST_AMOUNT, hexToString, MINIMAL_CONTRACT_DEPOSIT, stringToHex, Val, web3 } from "@alephium/web3";
 import { randomContractId, testAddress } from "@alephium/web3-test";
 import { DAOkitOrg } from "../artifacts/ts";
@@ -186,7 +185,7 @@ describe("DAOkitOrg unit tests", () => {
 
 	it("Private method should reject new URI when passed incorrect payload", async () => {
 		const NEW_URI = "https://example.com/newOrgFrontend";
-		const testResult = await DAOkitOrg.tests.setOrganizationUri({
+		await DAOkitOrg.tests.setOrganizationUri({
 			address: addressFromContractId(testOrgId),
 			initialAsset: {
 				alphAmount: MINIMAL_CONTRACT_DEPOSIT,
@@ -284,7 +283,7 @@ describe("DAOkitOrg unit tests", () => {
 			uintToByteVec(420, 2)
 		);
 		try {
-			const testResult = await DAOkitOrg.tests.setGovernanceRules({
+			await DAOkitOrg.tests.setGovernanceRules({
 				address: addressFromContractId(testOrgId),
 				initialAsset: {
 					alphAmount: MINIMAL_CONTRACT_DEPOSIT,
@@ -408,7 +407,7 @@ describe("DAOkitOrg unit tests", () => {
 		const TEST_AMOUNT = 42n * 10n**18n;
 		const TEST_RECIPIENT: Address = testAddress;
 		try {
-			const testResult = await DAOkitOrg.tests.transfer({
+			await DAOkitOrg.tests.transfer({
 				address: addressFromContractId(testOrgId),
 				initialAsset: {
 					alphAmount: MINIMAL_CONTRACT_DEPOSIT + DUST_AMOUNT,
@@ -451,7 +450,7 @@ describe("DAOkitOrg unit tests", () => {
 		const TEST_AMOUNT = 42n * 10n**18n;
 		const TEST_RECIPIENT: Address = testAddress;
 		try {
-			const testResult = await DAOkitOrg.tests.transfer({
+			await DAOkitOrg.tests.transfer({
 				address: addressFromContractId(testOrgId),
 				initialAsset: {
 					alphAmount: MINIMAL_CONTRACT_DEPOSIT + DUST_AMOUNT,
-- 
GitLab


From 09c4518e367e88465525cd60fa3ca98963b1617c Mon Sep 17 00:00:00 2001
From: Fabio Bonfiglio <fabio.bonfiglio@protonmail.ch>
Date: Fri, 21 Feb 2025 18:47:15 +0100
Subject: [PATCH 18/40] Move helper functions in a module

---
 src/lib/web-helpers.ts    | 28 ++++++++++++++++++++++++++++
 test/organization.test.ts | 30 ++----------------------------
 2 files changed, 30 insertions(+), 28 deletions(-)
 create mode 100644 src/lib/web-helpers.ts

diff --git a/src/lib/web-helpers.ts b/src/lib/web-helpers.ts
new file mode 100644
index 0000000..432bf33
--- /dev/null
+++ b/src/lib/web-helpers.ts
@@ -0,0 +1,28 @@
+import { binToHex, Val } from "@alephium/web3";
+
+/**
+ * Helper function similar to Ralph's built-in `u256To<N>Byte!()` functions.
+ * @param value The value to convert (should be comprised in the Ralph's U256 value range)
+ * @param bytes The number of big endian bytes to represent in ByteVec.
+ * @returns A string that can be passed as a ByteVec argument.
+ * @todo Put this in a module !
+ */
+export function uintToByteVec(value: bigint | number, bytes: number): string {
+	if (value < 0) throw new Error("Value must be an unsigned integer.");
+	const hexStr = value.toString(16).padStart(bytes*2, "0");
+	const buff = Buffer.alloc(bytes);
+	buff.write(hexStr, bytes - (hexStr.length / 2), "hex");
+	const byteVecStr = binToHex(buff);
+	if (byteVecStr.length > bytes*2) throw new Error(`Value (${value}) is too big to be encoded in ${bytes} bytes!`);
+	//console.debug(`Convert '${value}' into a ${bytes}-byte${bytes > 1 ? "s" : ""} ByteVec: '${byteVecStr}'`);
+	return byteVecStr;
+}
+
+/**
+ * Helper function to convert a returned ByteVec string into a BigInt.
+ * @param byteVec The ByteVec returned from a contract call
+ * @returns The value as a BigInt
+ */
+export function byteVecToUint(byteVec: Val): bigint {
+	return BigInt("0x" + byteVec.toString());
+}
diff --git a/test/organization.test.ts b/test/organization.test.ts
index c34266a..3b63368 100644
--- a/test/organization.test.ts
+++ b/test/organization.test.ts
@@ -1,38 +1,12 @@
-import { Address, addressFromContractId, ALPH_TOKEN_ID, binToHex, bs58, DUST_AMOUNT, hexToString, MINIMAL_CONTRACT_DEPOSIT, stringToHex, Val, web3 } from "@alephium/web3";
+import { Address, addressFromContractId, ALPH_TOKEN_ID, binToHex, bs58, DUST_AMOUNT, hexToString, MINIMAL_CONTRACT_DEPOSIT, stringToHex, web3 } from "@alephium/web3";
 import { randomContractId, testAddress } from "@alephium/web3-test";
 import { DAOkitOrg } from "../artifacts/ts";
+import { uintToByteVec, byteVecToUint } from "../src/lib/web-helpers";
 
 const TEST_ORG_URI = "https://example.com/orgFrontEnd";
 
 jest.setTimeout(process.env.CI ? 2000 : 400);
 
-/**
- * Helper function similar to Ralph's built-in `u256To<N>Byte!()` functions.
- * @param value The value to convert (should be comprised in the Ralph's U256 value range)
- * @param bytes The number of big endian bytes to represent in ByteVec.
- * @returns A string that can be passed as a ByteVec argument.
- * @todo Put this in a module !
- */
-function uintToByteVec(value: bigint | number, bytes: number): string {
-	if (value < 0) throw new Error("Value must be an unsigned integer.");
-	const hexStr = value.toString(16).padStart(bytes*2, "0");
-	const buff = Buffer.alloc(bytes);
-	buff.write(hexStr, bytes - (hexStr.length / 2), "hex");
-	const byteVecStr = binToHex(buff);
-	if (byteVecStr.length > bytes*2) throw new Error(`Value (${value}) is too big to be encoded in ${bytes} bytes!`);
-	//console.debug(`Convert '${value}' into a ${bytes}-byte${bytes > 1 ? "s" : ""} ByteVec: '${byteVecStr}'`);
-	return byteVecStr;
-}
-
-/**
- * Helper function to convert a returned ByteVec string into a BigInt.
- * @param byteVec The ByteVec returned from a contract call
- * @returns The value as a BigInt
- */
-function byteVecToUint(byteVec: Val): bigint {
-	return BigInt("0x" + byteVec.toString());
-}
-
 describe("DAOkitOrg unit tests", () => {
 	const testProposalTemplate = randomContractId();
 	const testBallotTemplate = randomContractId();
-- 
GitLab


From 636f1e844473ed803bf7b15ea66b2cb8e3d5cc25 Mon Sep 17 00:00:00 2001
From: Fabio Bonfiglio <fabio.bonfiglio@protonmail.ch>
Date: Fri, 21 Feb 2025 22:05:31 +0100
Subject: [PATCH 19/40] Remove useless debug msg

---
 test/organization.test.ts | 2 --
 1 file changed, 2 deletions(-)

diff --git a/test/organization.test.ts b/test/organization.test.ts
index 3b63368..7d4c7da 100644
--- a/test/organization.test.ts
+++ b/test/organization.test.ts
@@ -19,8 +19,6 @@ describe("DAOkitOrg unit tests", () => {
 
 	beforeAll(() => {
 		web3.setCurrentNodeProvider(`http://${process.env.CI ? "devnet" : "localhost"}:22973`, undefined, fetch);
-		console.debug(`Tests running on ${process.env.CI ? "CI devnet service" : "locally running devnet"}`);
-		console.debug("Test address (used as recipient, etc):", testAddress);
 	});
 
 	it("Should return correct Organization URI", async () => {
-- 
GitLab


From 59d06d20823cf3a5b75a0da25e1e30988bfc8961 Mon Sep 17 00:00:00 2001
From: Fabio Bonfiglio <fabio.bonfiglio@protonmail.ch>
Date: Fri, 21 Feb 2025 22:06:02 +0100
Subject: [PATCH 20/40] Add DAOkitProposal basic unit tests

---
 test/proposal.test.ts | 125 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 125 insertions(+)
 create mode 100644 test/proposal.test.ts

diff --git a/test/proposal.test.ts b/test/proposal.test.ts
new file mode 100644
index 0000000..b626eea
--- /dev/null
+++ b/test/proposal.test.ts
@@ -0,0 +1,125 @@
+import { addressFromContractId, MINIMAL_CONTRACT_DEPOSIT, stringToHex, web3 } from "@alephium/web3";
+import { randomContractId, testAddress } from "@alephium/web3-test";
+import { DAOkitProposal } from "../artifacts/ts";
+import { uintToByteVec, byteVecToUint } from "../src/lib/web-helpers";
+
+const TEST_METADATA_URI = "https://bafybeicfesp47qc6s5yoryps3osvv7fljdlrpk75wf2tyi6w2cjfj2alzi.ipfs.dweb.link/mdata.json";
+
+jest.setTimeout(process.env.CI ? 2000 : 400);
+
+describe("DAOkitProposal unit tests", () => {
+	const testOrgId = randomContractId();
+	const testProposalId = randomContractId();
+	const testBallotTemplate = randomContractId();
+	const authorENFTid = randomContractId();
+	const commitmentTokenId = randomContractId();
+	const commitmentDepositAmount = 20n * 10n**18n;
+	const testProposalIndex = 12n;
+	const commitPeriodEnd = BigInt(Date.now() + 86400000);
+	const votingPeriodEnd = commitPeriodEnd + 86400000n;
+	const testMaxVoters = Math.round(Math.random() * 4096);
+	const testRequiredQuorums = 20.2;
+	const GENERIC_MOTION_PAYLOAD = uintToByteVec(1, 1).concat(stringToHex(TEST_METADATA_URI));
+
+	beforeAll(() => {
+		web3.setCurrentNodeProvider(`http://${process.env.CI ? "devnet" : "localhost"}:22973`, undefined, fetch);
+	});
+
+	it("Should return correct org id and proposal index", async () => {
+		const maxVoters = uintToByteVec(testMaxVoters, 4);
+		const requiredQuorum = uintToByteVec(Math.round(10 * testRequiredQuorums), 2);
+		const state = uintToByteVec(1, 1);
+		const testResult = await DAOkitProposal.tests.getOrganizationIndex({
+			address: addressFromContractId(testProposalId),
+			initialAsset: {
+				alphAmount: MINIMAL_CONTRACT_DEPOSIT,
+				tokens: []
+			},
+			initialFields: {
+				authorAddress: testAddress,
+				authorENFTid,
+				ballotTemplateId: testBallotTemplate,
+				organizationId: testOrgId,
+				commitmentDepositTokenId: commitmentTokenId,
+				commitmentDepositAmount,
+				proposalIndex: testProposalIndex,
+				commitmentPeriodEnd: commitPeriodEnd,
+				votingPeriodEnd,
+				maxVoters,
+				requiredQuorum,
+				payload: GENERIC_MOTION_PAYLOAD,
+				state,
+				voteDiff: "80000000",
+				votedCount: "00000000",
+				committedCount: "00000000"
+			}
+		});
+		expect(testResult.returns.length).toEqual(2);
+		expect(testResult.returns[0]).toEqual(testOrgId);
+		expect(testResult.returns[1]).toEqual(testProposalIndex);
+	});
+
+	it("Should return correct state 1-byte ByteVec", async () => {
+		const maxVoters = uintToByteVec(testMaxVoters, 4);
+		const requiredQuorum = uintToByteVec(Math.round(10 * testRequiredQuorums), 2);
+		const state = uintToByteVec(4n, 1);
+		const testResult = await DAOkitProposal.tests.getProposalState({
+			address: addressFromContractId(testProposalId),
+			initialAsset: {
+				alphAmount: MINIMAL_CONTRACT_DEPOSIT,
+				tokens: []
+			},
+			initialFields: {
+				authorAddress: testAddress,
+				authorENFTid,
+				ballotTemplateId: testBallotTemplate,
+				organizationId: testOrgId,
+				commitmentDepositTokenId: commitmentTokenId,
+				commitmentDepositAmount,
+				proposalIndex: testProposalIndex,
+				commitmentPeriodEnd: commitPeriodEnd,
+				votingPeriodEnd,
+				maxVoters,
+				requiredQuorum,
+				payload: GENERIC_MOTION_PAYLOAD,
+				state,
+				voteDiff: "80000000",
+				votedCount: "00000000",
+				committedCount: "00000000"
+			}
+		});
+		expect(byteVecToUint(testResult.returns)).toEqual(4n);
+	});
+
+	it("Should return correct payload", async () => {
+		const maxVoters = uintToByteVec(testMaxVoters, 4);
+		const requiredQuorum = uintToByteVec(Math.round(10 * testRequiredQuorums), 2);
+		const state = uintToByteVec(1, 1);
+		const testResult = await DAOkitProposal.tests.getProposalPayload({
+			address: addressFromContractId(testProposalId),
+			initialAsset: {
+				alphAmount: MINIMAL_CONTRACT_DEPOSIT,
+				tokens: []
+			},
+			initialFields: {
+				authorAddress: testAddress,
+				authorENFTid,
+				ballotTemplateId: testBallotTemplate,
+				organizationId: testOrgId,
+				commitmentDepositTokenId: commitmentTokenId,
+				commitmentDepositAmount,
+				proposalIndex: testProposalIndex,
+				commitmentPeriodEnd: commitPeriodEnd,
+				votingPeriodEnd,
+				maxVoters,
+				requiredQuorum,
+				payload: GENERIC_MOTION_PAYLOAD,
+				state,
+				voteDiff: "80000000",
+				votedCount: "00000000",
+				committedCount: "00000000"
+			}
+		});
+		expect(testResult.returns).toEqual(GENERIC_MOTION_PAYLOAD);
+	});
+});
-- 
GitLab


From ba403f1760b935bb0a4c08d7a78ef357fcc05ec6 Mon Sep 17 00:00:00 2001
From: Fabio Bonfiglio <fabio.bonfiglio@protonmail.ch>
Date: Fri, 21 Feb 2025 22:06:11 +0100
Subject: [PATCH 21/40] Always verbose tests

---
 .gitlab-ci.yml | 2 +-
 package.json   | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 72ab527..f28edae 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -118,4 +118,4 @@ Test contracts:
     max: 2
     when: always
   script:
-    - npm run test
+    - npm test
diff --git a/package.json b/package.json
index b6fe45f..3e3817b 100644
--- a/package.json
+++ b/package.json
@@ -17,7 +17,7 @@
     "lint:frontend": "next lint",
     "lint:scripts": "eslint",
     "start": "next start",
-    "test": "npx @alephium/cli test"
+    "test": "npx @alephium/cli test -v"
   },
   "repository": {
     "type": "git+ssh",
-- 
GitLab


From 5f55c9eeb493efadb98283c7609c896f4d523c03 Mon Sep 17 00:00:00 2001
From: Fabio Bonfiglio <fabio.bonfiglio@protonmail.ch>
Date: Mon, 24 Feb 2025 16:35:24 +0100
Subject: [PATCH 22/40] Use constant for testes value

---
 test/proposal.test.ts | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/test/proposal.test.ts b/test/proposal.test.ts
index b626eea..34dcf77 100644
--- a/test/proposal.test.ts
+++ b/test/proposal.test.ts
@@ -62,7 +62,8 @@ describe("DAOkitProposal unit tests", () => {
 	it("Should return correct state 1-byte ByteVec", async () => {
 		const maxVoters = uintToByteVec(testMaxVoters, 4);
 		const requiredQuorum = uintToByteVec(Math.round(10 * testRequiredQuorums), 2);
-		const state = uintToByteVec(4n, 1);
+		const TEST_STATE = 4n;
+		const state = uintToByteVec(TEST_STATE, 1);
 		const testResult = await DAOkitProposal.tests.getProposalState({
 			address: addressFromContractId(testProposalId),
 			initialAsset: {
@@ -88,7 +89,7 @@ describe("DAOkitProposal unit tests", () => {
 				committedCount: "00000000"
 			}
 		});
-		expect(byteVecToUint(testResult.returns)).toEqual(4n);
+		expect(byteVecToUint(testResult.returns)).toEqual(TEST_STATE);
 	});
 
 	it("Should return correct payload", async () => {
-- 
GitLab


From b0e2049a1ed07b7dfafadb4898a3400dca5a45f6 Mon Sep 17 00:00:00 2001
From: Fabio Bonfiglio <fabio.bonfiglio@protonmail.ch>
Date: Mon, 24 Feb 2025 18:48:42 +0100
Subject: [PATCH 23/40] TODOs for proposal states tests

---
 test/proposal.test.ts | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/test/proposal.test.ts b/test/proposal.test.ts
index 34dcf77..0d30d73 100644
--- a/test/proposal.test.ts
+++ b/test/proposal.test.ts
@@ -123,4 +123,16 @@ describe("DAOkitProposal unit tests", () => {
 		});
 		expect(testResult.returns).toEqual(GENERIC_MOTION_PAYLOAD);
 	});
+
+	it.todo("Should change state from Proposed to Dropped, and return the locked token, if quorum not reached at the end of the commitment period");
+
+	it.todo("Should change state from Proposed to Voting when quorum reached at the end of the commitment period");
+
+	it.todo("Should return the locked token and change state from Voting to Rejected if majority not reached when voting period ends");
+
+	it.todo("Should return the locked token and change state from Voting to Accepted → Executing when majority is reached");
+
+	it.todo("Shouldn't change state when in Proposed and commitment period not ended");
+
+	it.todo("Shouldn't change state when in Voting and conditions (no majority reached and period not ended) for closing vote aren't met");
 });
-- 
GitLab


From e19ae8f88b687c33d0e4af7a98b297072b6dc50a Mon Sep 17 00:00:00 2001
From: Fabio Bonfiglio <fabio.bonfiglio@protonmail.ch>
Date: Mon, 24 Feb 2025 18:52:39 +0100
Subject: [PATCH 24/40] Correct returning locked token scenario

---
 test/proposal.test.ts | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/test/proposal.test.ts b/test/proposal.test.ts
index 0d30d73..4bca8fa 100644
--- a/test/proposal.test.ts
+++ b/test/proposal.test.ts
@@ -126,11 +126,11 @@ describe("DAOkitProposal unit tests", () => {
 
 	it.todo("Should change state from Proposed to Dropped, and return the locked token, if quorum not reached at the end of the commitment period");
 
-	it.todo("Should change state from Proposed to Voting when quorum reached at the end of the commitment period");
+	it.todo("Should change state from Proposed to Voting, and return the locked token, when quorum reached at the end of the commitment period");
 
-	it.todo("Should return the locked token and change state from Voting to Rejected if majority not reached when voting period ends");
+	it.todo("Should change state from Voting to Rejected if majority not reached when voting period ends");
 
-	it.todo("Should return the locked token and change state from Voting to Accepted → Executing when majority is reached");
+	it.todo("Should change state from Voting to Accepted → Executing when majority is reached");
 
 	it.todo("Shouldn't change state when in Proposed and commitment period not ended");
 
-- 
GitLab


From 8dc414f14458a21db650db7bfc0fa07005e18068 Mon Sep 17 00:00:00 2001
From: Fabio Bonfiglio <fabio.bonfiglio@protonmail.ch>
Date: Mon, 24 Feb 2025 18:54:23 +0100
Subject: [PATCH 25/40] TODO for vote close conditions

---
 contracts/proposal.ral | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/contracts/proposal.ral b/contracts/proposal.ral
index fd90d9a..beae2f0 100644
--- a/contracts/proposal.ral
+++ b/contracts/proposal.ral
@@ -76,7 +76,7 @@ Contract DAOkitProposal(
 			destroySelf!(authorAddress) // ??
 		}
 		else if (state == ProposalStates.Voting) {
-			if (votingPeriodEnd <= blockTimeStamp!()) {
+			if (votingPeriodEnd <= blockTimeStamp!()) { // TODO: Change to consider vote closed before period ends when a majority is reached.
 				state = if (u256From4Byte!(voteDiff) >= 2147483648u)
 					ProposalStates.Accepted
 				else
-- 
GitLab


From 576d6c5436c5b46f2004ae9b464f7a80a79242d6 Mon Sep 17 00:00:00 2001
From: Fabio Bonfiglio <fabio.bonfiglio@protonmail.ch>
Date: Tue, 25 Feb 2025 15:32:05 +0100
Subject: [PATCH 26/40] More explicit name

---
 src/lib/{web-helpers.ts => ralph-helpers.ts} | 1 -
 test/organization.test.ts                    | 2 +-
 test/proposal.test.ts                        | 2 +-
 3 files changed, 2 insertions(+), 3 deletions(-)
 rename src/lib/{web-helpers.ts => ralph-helpers.ts} (97%)

diff --git a/src/lib/web-helpers.ts b/src/lib/ralph-helpers.ts
similarity index 97%
rename from src/lib/web-helpers.ts
rename to src/lib/ralph-helpers.ts
index 432bf33..c4261d6 100644
--- a/src/lib/web-helpers.ts
+++ b/src/lib/ralph-helpers.ts
@@ -5,7 +5,6 @@ import { binToHex, Val } from "@alephium/web3";
  * @param value The value to convert (should be comprised in the Ralph's U256 value range)
  * @param bytes The number of big endian bytes to represent in ByteVec.
  * @returns A string that can be passed as a ByteVec argument.
- * @todo Put this in a module !
  */
 export function uintToByteVec(value: bigint | number, bytes: number): string {
 	if (value < 0) throw new Error("Value must be an unsigned integer.");
diff --git a/test/organization.test.ts b/test/organization.test.ts
index 7d4c7da..3771e5c 100644
--- a/test/organization.test.ts
+++ b/test/organization.test.ts
@@ -1,7 +1,7 @@
 import { Address, addressFromContractId, ALPH_TOKEN_ID, binToHex, bs58, DUST_AMOUNT, hexToString, MINIMAL_CONTRACT_DEPOSIT, stringToHex, web3 } from "@alephium/web3";
 import { randomContractId, testAddress } from "@alephium/web3-test";
 import { DAOkitOrg } from "../artifacts/ts";
-import { uintToByteVec, byteVecToUint } from "../src/lib/web-helpers";
+import { uintToByteVec, byteVecToUint } from "../src/lib/ralph-helpers";
 
 const TEST_ORG_URI = "https://example.com/orgFrontEnd";
 
diff --git a/test/proposal.test.ts b/test/proposal.test.ts
index 4bca8fa..cb6caba 100644
--- a/test/proposal.test.ts
+++ b/test/proposal.test.ts
@@ -1,7 +1,7 @@
 import { addressFromContractId, MINIMAL_CONTRACT_DEPOSIT, stringToHex, web3 } from "@alephium/web3";
 import { randomContractId, testAddress } from "@alephium/web3-test";
 import { DAOkitProposal } from "../artifacts/ts";
-import { uintToByteVec, byteVecToUint } from "../src/lib/web-helpers";
+import { uintToByteVec, byteVecToUint } from "../src/lib/ralph-helpers";
 
 const TEST_METADATA_URI = "https://bafybeicfesp47qc6s5yoryps3osvv7fljdlrpk75wf2tyi6w2cjfj2alzi.ipfs.dweb.link/mdata.json";
 
-- 
GitLab


From b51ecf117569e231217ca23a207952c1904a7c18 Mon Sep 17 00:00:00 2001
From: Fabio Bonfiglio <fabio.bonfiglio@protonmail.ch>
Date: Tue, 25 Feb 2025 18:51:20 +0100
Subject: [PATCH 27/40] Add tests for states changes at commitment period ends
 and voting (WIP)

---
 test/proposal.test.ts | 162 ++++++++++++++++++++++++++++++++++++++----
 1 file changed, 150 insertions(+), 12 deletions(-)

diff --git a/test/proposal.test.ts b/test/proposal.test.ts
index cb6caba..1b999bc 100644
--- a/test/proposal.test.ts
+++ b/test/proposal.test.ts
@@ -15,9 +15,11 @@ describe("DAOkitProposal unit tests", () => {
 	const commitmentTokenId = randomContractId();
 	const commitmentDepositAmount = 20n * 10n**18n;
 	const testProposalIndex = 12n;
-	const commitPeriodEnd = BigInt(Date.now() + 86400000);
-	const votingPeriodEnd = commitPeriodEnd + 86400000n;
-	const testMaxVoters = Math.round(Math.random() * 4096);
+	const ONE_DAY = 24000n * 3600n;
+	const FOUR_HOURS = 4000n * 3600n;
+	const testCommitPeriodEnd = BigInt(Date.now()) + ONE_DAY;
+	const testVotingPeriodEnd = testCommitPeriodEnd + ONE_DAY;
+	const testMaxVoters = Math.round(Math.random() * 4096) + 420;
 	const testRequiredQuorums = 20.2;
 	const GENERIC_MOTION_PAYLOAD = uintToByteVec(1, 1).concat(stringToHex(TEST_METADATA_URI));
 
@@ -43,8 +45,8 @@ describe("DAOkitProposal unit tests", () => {
 				commitmentDepositTokenId: commitmentTokenId,
 				commitmentDepositAmount,
 				proposalIndex: testProposalIndex,
-				commitmentPeriodEnd: commitPeriodEnd,
-				votingPeriodEnd,
+				commitmentPeriodEnd: testCommitPeriodEnd,
+				votingPeriodEnd: testVotingPeriodEnd,
 				maxVoters,
 				requiredQuorum,
 				payload: GENERIC_MOTION_PAYLOAD,
@@ -78,8 +80,8 @@ describe("DAOkitProposal unit tests", () => {
 				commitmentDepositTokenId: commitmentTokenId,
 				commitmentDepositAmount,
 				proposalIndex: testProposalIndex,
-				commitmentPeriodEnd: commitPeriodEnd,
-				votingPeriodEnd,
+				commitmentPeriodEnd: testCommitPeriodEnd,
+				votingPeriodEnd: testVotingPeriodEnd,
 				maxVoters,
 				requiredQuorum,
 				payload: GENERIC_MOTION_PAYLOAD,
@@ -110,8 +112,8 @@ describe("DAOkitProposal unit tests", () => {
 				commitmentDepositTokenId: commitmentTokenId,
 				commitmentDepositAmount,
 				proposalIndex: testProposalIndex,
-				commitmentPeriodEnd: commitPeriodEnd,
-				votingPeriodEnd,
+				commitmentPeriodEnd: testCommitPeriodEnd,
+				votingPeriodEnd: testVotingPeriodEnd,
 				maxVoters,
 				requiredQuorum,
 				payload: GENERIC_MOTION_PAYLOAD,
@@ -124,11 +126,147 @@ describe("DAOkitProposal unit tests", () => {
 		expect(testResult.returns).toEqual(GENERIC_MOTION_PAYLOAD);
 	});
 
-	it.todo("Should change state from Proposed to Dropped, and return the locked token, if quorum not reached at the end of the commitment period");
+	it("Should change state from Proposed to Dropped if quorum not reached at the end of the commitment period", async () => {
+		const maxVoters = uintToByteVec(testMaxVoters, 4);
+		const requiredQuorum = uintToByteVec(Math.round(10 * testRequiredQuorums), 2);
+		const committedCount = uintToByteVec(Math.round((testRequiredQuorums - 5.2) * testMaxVoters / 100), 4);
+		const state = "01"; // Proposed
+		const commitmentPeriodEnd = BigInt(Date.now()) - FOUR_HOURS;
+		const votingPeriodEnd = commitmentPeriodEnd + ONE_DAY;
+		const EXPECTED_STATE = "03"; // Dropped
+		const testResult = await DAOkitProposal.tests.updateState({
+			address: addressFromContractId(testProposalId),
+			initialAsset: {
+				alphAmount: MINIMAL_CONTRACT_DEPOSIT,
+				tokens: [
+					{
+						id: authorENFTid,
+						amount: 1n
+					}
+				]
+			},
+			initialFields: {
+				authorAddress: addressFromContractId(testProposalId),
+				authorENFTid,
+				ballotTemplateId: testBallotTemplate,
+				organizationId: testOrgId,
+				commitmentDepositTokenId: commitmentTokenId,
+				commitmentDepositAmount,
+				proposalIndex: testProposalIndex,
+				commitmentPeriodEnd,
+				votingPeriodEnd,
+				maxVoters,
+				requiredQuorum,
+				payload: GENERIC_MOTION_PAYLOAD,
+				state,
+				voteDiff: "80000000",
+				votedCount: "00000000",
+				committedCount
+			}
+		});
+		expect(testResult.returns).toEqual(EXPECTED_STATE);
+		expect(testResult.contracts[0].fields.state).toEqual(EXPECTED_STATE);
+		expect(testResult.events.length).toEqual(1);
+		expect(testResult.events[0].contractAddress).toEqual(addressFromContractId(testProposalId));
+		expect(testResult.events[0].name).toEqual("ProposalStateChanged");
+		expect(testResult.events[0].fields.updatedState).toEqual(EXPECTED_STATE);
+	});
 
-	it.todo("Should change state from Proposed to Voting, and return the locked token, when quorum reached at the end of the commitment period");
+	it.todo("Transfers (like returning the locked token) are to be tested in integration test");
 
-	it.todo("Should change state from Voting to Rejected if majority not reached when voting period ends");
+	it("Should change state from Proposed to Voting when quorum reached at the end of the commitment period", async () => {
+		const maxVoters = uintToByteVec(testMaxVoters, 4);
+		const requiredQuorum = uintToByteVec(Math.round(10 * testRequiredQuorums), 2);
+		const committedCount = uintToByteVec(Math.round((testRequiredQuorums + 4.2) * testMaxVoters / 100), 4);
+		const state = "01"; // Proposed
+		const commitmentPeriodEnd = BigInt(Date.now()) - FOUR_HOURS;
+		const votingPeriodEnd = commitmentPeriodEnd + ONE_DAY;
+		const EXPECTED_STATE = "04"; // Voting
+		const testResult = await DAOkitProposal.tests.updateState({
+			address: addressFromContractId(testProposalId),
+			initialAsset: {
+				alphAmount: MINIMAL_CONTRACT_DEPOSIT,
+				tokens: [
+					{
+						id: authorENFTid,
+						amount: 1n
+					}
+				]
+			},
+			initialFields: {
+				authorAddress: addressFromContractId(testProposalId),
+				authorENFTid,
+				ballotTemplateId: testBallotTemplate,
+				organizationId: testOrgId,
+				commitmentDepositTokenId: commitmentTokenId,
+				commitmentDepositAmount,
+				proposalIndex: testProposalIndex,
+				commitmentPeriodEnd,
+				votingPeriodEnd,
+				maxVoters,
+				requiredQuorum,
+				payload: GENERIC_MOTION_PAYLOAD,
+				state,
+				voteDiff: "80000000",
+				votedCount: "00000000",
+				committedCount
+			}
+		});
+		expect(testResult.returns).toEqual(EXPECTED_STATE);
+		expect(testResult.contracts[0].fields.state).toEqual(EXPECTED_STATE);
+		expect(testResult.events.length).toEqual(1);
+		expect(testResult.events[0].contractAddress).toEqual(addressFromContractId(testProposalId));
+		expect(testResult.events[0].name).toEqual("ProposalStateChanged");
+		expect(testResult.events[0].fields.updatedState).toEqual(EXPECTED_STATE);
+	});
+
+	it("Should change state from Voting to Rejected if majority not reached when voting period ends", async () => {
+		const maxVoters = uintToByteVec(testMaxVoters, 4);
+		const requiredQuorum = uintToByteVec(Math.round(10 * testRequiredQuorums), 2);
+		const committedCount = uintToByteVec(Math.round((testRequiredQuorums + 4.2) * testMaxVoters / 100), 4);
+		const votedCount = uintToByteVec(byteVecToUint(committedCount) - 2n, 4);
+		const voteDiff = uintToByteVec(2147000000n, 4);
+		const state = "04"; // Voting
+		const commitmentPeriodEnd = BigInt(Date.now()) - FOUR_HOURS;
+		const votingPeriodEnd = commitmentPeriodEnd + 3600000n;
+		const EXPECTED_STATE = "06"; // Rejected
+		const testResult = await DAOkitProposal.tests.updateState({
+			address: addressFromContractId(testProposalId),
+			initialAsset: {
+				alphAmount: MINIMAL_CONTRACT_DEPOSIT,
+				tokens: [
+					{
+						id: authorENFTid,
+						amount: 1n
+					}
+				]
+			},
+			initialFields: {
+				authorAddress: addressFromContractId(testProposalId),
+				authorENFTid,
+				ballotTemplateId: testBallotTemplate,
+				organizationId: testOrgId,
+				commitmentDepositTokenId: commitmentTokenId,
+				commitmentDepositAmount,
+				proposalIndex: testProposalIndex,
+				commitmentPeriodEnd,
+				votingPeriodEnd,
+				maxVoters,
+				requiredQuorum,
+				payload: GENERIC_MOTION_PAYLOAD,
+				state,
+				voteDiff,
+				votedCount,
+				committedCount
+			}
+		});
+		expect(testResult.returns).toEqual(EXPECTED_STATE);
+		expect(testResult.contracts[0].fields.state).toEqual(EXPECTED_STATE);
+		expect(testResult.events.length).toEqual(1);
+		expect(testResult.events[0].contractAddress).toEqual(addressFromContractId(testProposalId));
+		expect(testResult.events[0].name).toEqual("ProposalStateChanged");
+		expect(testResult.events[0].fields.updatedState).toEqual(EXPECTED_STATE);
+	});
 
 	it.todo("Should change state from Voting to Accepted → Executing when majority is reached");
 
-- 
GitLab


From e92f77e879e6ec7fcecdd610ee2fdfd32fa1ead0 Mon Sep 17 00:00:00 2001
From: Fabio Bonfiglio <fabio.bonfiglio@protonmail.ch>
Date: Tue, 25 Feb 2025 21:57:42 +0100
Subject: [PATCH 28/40] Change to absolute counting model

---
 .project.json                     |  4 ++--
 artifacts/DAOkitProposal.ral.json | 23 +++++++++++++++----
 artifacts/ts/DAOkitProposal.ts    | 29 +++++++++++++++++++----
 contracts/proposal.ral            | 38 +++++++++++++++++++++++--------
 4 files changed, 73 insertions(+), 21 deletions(-)

diff --git a/.project.json b/.project.json
index c1d3603..5d63dc0 100644
--- a/.project.json
+++ b/.project.json
@@ -19,9 +19,9 @@
     },
     "DAOkitProposal": {
       "sourceFile": "proposal.ral",
-      "sourceCodeHash": "dfe71c855f9b2cbb67997def077eecb514cd06f0b8f9537aee8b72bbae3be44c",
+      "sourceCodeHash": "b1a033258932250fb55dcd8f9dfe89ffa4aa22a4835a5dc0d47892c2471ed1ad",
       "bytecodeDebugPatch": "",
-      "codeHashDebug": "70709b52b21909e6b1971f45e606047669615e84ded9e11b8e4e8c11e8a2c425"
+      "codeHashDebug": "42231ed9eb7694d753576752d0cb79863549e6dbfc2a0dc726bf3ec68decd48e"
     },
     "DAOkitVotingBallot": {
       "sourceFile": "ballot.ral",
diff --git a/artifacts/DAOkitProposal.ral.json b/artifacts/DAOkitProposal.ral.json
index d6d7be6..e6703dd 100644
--- a/artifacts/DAOkitProposal.ral.json
+++ b/artifacts/DAOkitProposal.ral.json
@@ -1,8 +1,8 @@
 {
   "version": "v3.11.1",
   "name": "DAOkitProposal",
-  "bytecode": "100a101e402c408f40cc40e240fa412d41c941df010000000204d36590af8fce03ce0602010000000103d3d0148c55a00102010000000103d31de3aebea0000201030306004035d3e5372129160000091341917b1600ce01421341937b1600cb17031603c54c07160116020e0c1603d4ed37c7eb4a1cb11301641602160113026417051704b47ad1a2ce04ce05a31600ce0216041605c118a0046e0d2a68a10400081801000304004021d323723c7d16001601441602444e17030c0db3d47fff4ea11603411341907ba0036e0d2a68a103a0026e16006c0d2b2a68a102000818010301010107d3c9b5c135160000091341917b000802010000000008d3b573a9b4b3ce03411341937b140108a10101000202001bd34d603decb3ce03411600ce00451a1341937ba00114010141a0000c0d6216010c0d62411a1341907b1601a10000020001014052a0011700a001140101414c1bce0756324c14ce0a6d1343e8a0046e2cce096e2d324c021401044a01140103a101ce00ce010dab4a02a001024a1ea001140102414c03ce00b04a17a001140104414c0ece0856324c09a0026e13c080000000344c021401054a01140106a1014a051341a2047c7b181600a001424c0da001140105414c06140107a1010c0cce03010b06a0015ea0010200000101010cb41600a50d2f16000d0dce0301051a02",
-  "codeHash": "70709b52b21909e6b1971f45e606047669615e84ded9e11b8e4e8c11e8a2c425",
+  "bytecode": "100a101e402c408f40e840fe41164149421c4232010000000204d36590af8fce03ce0602010000000103d3d0148c55a00102010000000103d31de3aebea0000201030306004035d3e5372129160000091341917b1600ce01421341937b1600cb17031603c54c07160116020e0c1603d4ed37c7eb4a1cb11301641602160113026417051704b47ad1a2ce04ce05a31600ce0216041605c118a0046e0d2a68a10400081801000305004034d323723c7d16001601441602444e17030c0db3d47fff4ea11603411341907b16006c170416040c344c1316040e2e0c2f4c07a0026e0d2a68a1024a06a0036e0d2a68a10306a0046ea0026ea0036e60000818010301010107d3c9b5c135160000091341917b000802010000000008d3b573a9b4b3ce03411341937b140108a10101000202001bd34d603decb3ce03411600ce00451a1341937ba00114010141a0000c0d6216010c0d62411a1341907b1601a10000020003014073a0011700a001140101414c1bce0756324c14ce0a6d1343e8a0046e2cce096e2d324c021401044a01140103a101ce00ce010dab4a02a001024a403fa001140102414c03ce00b04a4038a001140104414c402fa0046e170116010e2d16010e2e2a0d2a1702ce0856324c0ba0026ea0036e334c021401054a01140106a1014a12a0026e1602344c03140105a1014a0aa0036e1602344c03140106a1014a02140104a1014a051341a2047c7b181600a001424c0da001140105414c06140107a1010c0cce03010b07a0015ea0010200000101010cb41600a50d2f16000d0dce0301051a02",
+  "codeHash": "42231ed9eb7694d753576752d0cb79863549e6dbfc2a0dc726bf3ec68decd48e",
   "fieldsSig": {
     "names": [
       "authorAddress",
@@ -18,8 +18,8 @@
       "requiredQuorum",
       "payload",
       "state",
-      "voteDiff",
-      "votedCount",
+      "acceptedCount",
+      "rejectedCount",
       "committedCount"
     ],
     "types": [
@@ -69,6 +69,19 @@
         "ByteVec"
       ]
     },
+    {
+      "name": "ProposalVote",
+      "fieldNames": [
+        "participants",
+        "accepted",
+        "rejected"
+      ],
+      "fieldTypes": [
+        "U256",
+        "U256",
+        "U256"
+      ]
+    },
     {
       "name": "ProposalStateChanged",
       "fieldNames": [
@@ -248,7 +261,7 @@
           }
         },
         {
-          "name": "Refused",
+          "name": "Cancelled",
           "value": {
             "type": "ByteVec",
             "value": "02"
diff --git a/artifacts/ts/DAOkitProposal.ts b/artifacts/ts/DAOkitProposal.ts
index cb18036..65c69db 100644
--- a/artifacts/ts/DAOkitProposal.ts
+++ b/artifacts/ts/DAOkitProposal.ts
@@ -52,8 +52,8 @@ export namespace DAOkitProposalTypes {
     requiredQuorum: HexString;
     payload: HexString;
     state: HexString;
-    voteDiff: HexString;
-    votedCount: HexString;
+    acceptedCount: HexString;
+    rejectedCount: HexString;
     committedCount: HexString;
   };
 
@@ -62,6 +62,11 @@ export namespace DAOkitProposalTypes {
   export type ProposalUpdatedEvent = ContractEvent<{
     updatedPayload: HexString;
   }>;
+  export type ProposalVoteEvent = ContractEvent<{
+    participants: bigint;
+    accepted: bigint;
+    rejected: bigint;
+  }>;
   export type ProposalStateChangedEvent = ContractEvent<{
     updatedState: HexString;
   }>;
@@ -203,7 +208,7 @@ class Factory extends ContractFactory<
     );
   }
 
-  eventIndex = { ProposalUpdated: 0, ProposalStateChanged: 1 };
+  eventIndex = { ProposalUpdated: 0, ProposalVote: 1, ProposalStateChanged: 2 };
   consts = {
     ErrorCodes: {
       BadRequest: BigInt("400"),
@@ -212,7 +217,7 @@ class Factory extends ContractFactory<
     },
     ProposalStates: {
       Proposed: "01",
-      Refused: "02",
+      Cancelled: "02",
       Dropped: "03",
       Voting: "04",
       Accepted: "05",
@@ -347,7 +352,7 @@ export const DAOkitProposal = new Factory(
   Contract.fromJson(
     DAOkitProposalContractJson,
     "",
-    "70709b52b21909e6b1971f45e606047669615e84ded9e11b8e4e8c11e8a2c425",
+    "42231ed9eb7694d753576752d0cb79863549e6dbfc2a0dc726bf3ec68decd48e",
     []
   )
 );
@@ -380,6 +385,19 @@ export class DAOkitProposalInstance extends ContractInstance {
     );
   }
 
+  subscribeProposalVoteEvent(
+    options: EventSubscribeOptions<DAOkitProposalTypes.ProposalVoteEvent>,
+    fromCount?: number
+  ): EventSubscription {
+    return subscribeContractEvent(
+      DAOkitProposal.contract,
+      this,
+      options,
+      "ProposalVote",
+      fromCount
+    );
+  }
+
   subscribeProposalStateChangedEvent(
     options: EventSubscribeOptions<DAOkitProposalTypes.ProposalStateChangedEvent>,
     fromCount?: number
@@ -396,6 +414,7 @@ export class DAOkitProposalInstance extends ContractInstance {
   subscribeAllEvents(
     options: EventSubscribeOptions<
       | DAOkitProposalTypes.ProposalUpdatedEvent
+      | DAOkitProposalTypes.ProposalVoteEvent
       | DAOkitProposalTypes.ProposalStateChangedEvent
     >,
     fromCount?: number
diff --git a/contracts/proposal.ral b/contracts/proposal.ral
index beae2f0..78958ea 100644
--- a/contracts/proposal.ral
+++ b/contracts/proposal.ral
@@ -12,12 +12,13 @@ Contract DAOkitProposal(
 	requiredQuorum: ByteVec, // Expressed in ‰ (per mille) and stored in 2-bytes integer
 	mut payload: ByteVec,
 	mut state: ByteVec, // 1 Byte (see https://gitlab.fbo.network/alephium/daokit/-/wikis/Concept#proposal-lifecycle)
-	mut voteDiff: ByteVec, // 4 Bytes (init to 0x80000000)
-	mut votedCount: ByteVec, // 4 Bytes
+	mut acceptedCount: ByteVec, // 4 Bytes (init to 0x00000000)
+	mut rejectedCount: ByteVec, // 4 Bytes (init to 0x00000000)
 	mut committedCount: ByteVec // 4 Bytes
 ) implements IDAOkitProposal {
 
 	event ProposalUpdated(updatedPayload: ByteVec)
+	event ProposalVote(participants: U256, accepted: U256, rejected: U256)
 	event ProposalStateChanged(updatedState: ByteVec)
 
 	enum ErrorCodes {
@@ -29,7 +30,7 @@ Contract DAOkitProposal(
 
 	enum ProposalStates {
 		Proposed = #01
-		Refused = #02
+		Cancelled = #02
 		Dropped = #03
 		Voting = #04
 		Accepted = #05
@@ -72,16 +73,27 @@ Contract DAOkitProposal(
 				return state
 			}
 		}
-		else if (state == ProposalStates.Refused) {
-			destroySelf!(authorAddress) // ??
+		else if (state == ProposalStates.Cancelled) {
+			destroySelf!(authorAddress)
 		}
 		else if (state == ProposalStates.Voting) {
-			if (votingPeriodEnd <= blockTimeStamp!()) { // TODO: Change to consider vote closed before period ends when a majority is reached.
-				state = if (u256From4Byte!(voteDiff) >= 2147483648u)
+			let participants = u256From4Byte!(committedCount)
+			let absoluteMajority = participants / 2u + participants % 2u + 1u
+			if (votingPeriodEnd <= blockTimeStamp!()) {
+				state = if (u256From4Byte!(acceptedCount) > u256From4Byte!(rejectedCount))
 					ProposalStates.Accepted
 				else
 					ProposalStates.Rejected
 			}
+			else if (u256From4Byte!(acceptedCount) >= absoluteMajority) {
+				state = ProposalStates.Accepted
+			}
+			else if (u256From4Byte!(rejectedCount) >= absoluteMajority) {
+				state = ProposalStates.Rejected
+			}
+			else {
+				state = ProposalStates.Voting
+			}
 		}
 		else {
 			panic!(418u)
@@ -129,8 +141,16 @@ Contract DAOkitProposal(
 	pub fn revealVote(vote: ByteVec, nonce: ByteVec, revealer: ByteVec) -> () {
 		let verifiedCommitment = blake2b!(vote ++ nonce ++ revealer)
 		checkCaller!(IDAOkitVotingBallot(callerContractId!()).getCommitment() == verifiedCommitment, ErrorCodes.BadRequest)
-		votedCount = u256To4Byte!(u256From4Byte!(votedCount) + 1u)
-		voteDiff = u256To4Byte!(u256From4Byte!(voteDiff) + (u256From1Byte!(vote) - 1u))
+		let guessableVote = u256From1Byte!(vote)
+		if (guessableVote >= 0u) {
+			if (guessableVote % 2u == 0u) {
+				acceptedCount = u256To4Byte!(u256From4Byte!(acceptedCount) + 1u)
+			}
+			else {
+				rejectedCount = u256To4Byte!(u256From4Byte!(rejectedCount) + 1u)
+			}
+		}
+		emit ProposalVote(u256From4Byte!(committedCount), u256From4Byte!(acceptedCount), u256From4Byte!(rejectedCount))
 		let _ = updateState()
 	}
 
-- 
GitLab


From 08cd6ae7d9e123c37e99497a67c17a797e2ee57e Mon Sep 17 00:00:00 2001
From: Fabio Bonfiglio <fabio.bonfiglio@protonmail.ch>
Date: Wed, 26 Feb 2025 14:21:10 +0100
Subject: [PATCH 29/40] Remove useless comment

---
 contracts/interfaces/proposal_interface.ral | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/contracts/interfaces/proposal_interface.ral b/contracts/interfaces/proposal_interface.ral
index e7e2a43..d659dcf 100644
--- a/contracts/interfaces/proposal_interface.ral
+++ b/contracts/interfaces/proposal_interface.ral
@@ -31,6 +31,3 @@ Interface IDAOkitProposal {
 	// Set the state as enacted. Can only be called by the org
 	pub fn confirmExecution() -> ()
 }
-
-// TODO:
-// - derived variants for motions, governance rules change, grant proposal, etc.
\ No newline at end of file
-- 
GitLab


From 4d00a967df5cd9f935d951c192b5b8dd7511f8aa Mon Sep 17 00:00:00 2001
From: Fabio Bonfiglio <fabio.bonfiglio@protonmail.ch>
Date: Wed, 26 Feb 2025 14:22:57 +0100
Subject: [PATCH 30/40] Correct absolute majority calculation

---
 contracts/proposal.ral | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/contracts/proposal.ral b/contracts/proposal.ral
index 78958ea..12c78de 100644
--- a/contracts/proposal.ral
+++ b/contracts/proposal.ral
@@ -77,8 +77,7 @@ Contract DAOkitProposal(
 			destroySelf!(authorAddress)
 		}
 		else if (state == ProposalStates.Voting) {
-			let participants = u256From4Byte!(committedCount)
-			let absoluteMajority = participants / 2u + participants % 2u + 1u
+			let absoluteMajority = u256From4Byte!(committedCount) / 2u + 1u
 			if (votingPeriodEnd <= blockTimeStamp!()) {
 				state = if (u256From4Byte!(acceptedCount) > u256From4Byte!(rejectedCount))
 					ProposalStates.Accepted
-- 
GitLab


From 71311b8cf165115347e47c6bae2f519b7eb50301 Mon Sep 17 00:00:00 2001
From: Fabio Bonfiglio <fabio.bonfiglio@protonmail.ch>
Date: Wed, 26 Feb 2025 14:28:15 +0100
Subject: [PATCH 31/40] Fix subcontract id definition

---
 .project.json                     | 8 ++++----
 artifacts/DAOkitOrg.ral.json      | 4 ++--
 artifacts/DAOkitProposal.ral.json | 4 ++--
 artifacts/ts/DAOkitOrg.ts         | 2 +-
 artifacts/ts/DAOkitProposal.ts    | 2 +-
 contracts/organization.ral        | 4 ++--
 6 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/.project.json b/.project.json
index 5d63dc0..9de3e82 100644
--- a/.project.json
+++ b/.project.json
@@ -13,15 +13,15 @@
   "infos": {
     "DAOkitOrg": {
       "sourceFile": "organization.ral",
-      "sourceCodeHash": "9723be23a1f1daf999215626c6616adbb311198e49f54e5850ae1ccf90de135f",
+      "sourceCodeHash": "c65b9bf2b7189cee99ee0db7e210c3aa5fc650ea7fddd6f08702f44c26bd94f7",
       "bytecodeDebugPatch": "",
-      "codeHashDebug": "a3fc9de272220d02f20a1e7158c869acaf75c14f4db645c9a3d73721700debf3"
+      "codeHashDebug": "44443d439acd9891b3f8b98cc31173e3828fc89fd7a38c298c2798daa491866e"
     },
     "DAOkitProposal": {
       "sourceFile": "proposal.ral",
-      "sourceCodeHash": "b1a033258932250fb55dcd8f9dfe89ffa4aa22a4835a5dc0d47892c2471ed1ad",
+      "sourceCodeHash": "20173d572dfa8106ca569868bb8e8d3ffef2da8e917543fb4adf7d8ab67b8466",
       "bytecodeDebugPatch": "",
-      "codeHashDebug": "42231ed9eb7694d753576752d0cb79863549e6dbfc2a0dc726bf3ec68decd48e"
+      "codeHashDebug": "57d32f70aa33e4db44e99e03a7defe1d7078479cda98eb7fbbd92de9c9cb9539"
     },
     "DAOkitVotingBallot": {
       "sourceFile": "ballot.ral",
diff --git a/artifacts/DAOkitOrg.ral.json b/artifacts/DAOkitOrg.ral.json
index 60cbc71..5f8061c 100644
--- a/artifacts/DAOkitOrg.ral.json
+++ b/artifacts/DAOkitOrg.ral.json
@@ -1,8 +1,8 @@
 {
   "version": "v3.11.1",
   "name": "DAOkitOrg",
-  "bytecode": "0b0c0e1c403940474062408a414c416d419241ee423b42e3010000000103d3facdcc89a00002010000000103d3926e1fc1a0010201000000070cd3fa5d1fcba001a0026fa0036fa0046fa005a006a00702010000000103d3d22165c1a0080201000102010bd3ecf235d9160040cb17011601c51341947b160102010001030112d30fec8d94160017010c0e160101011817021602a001410c0d1601d4fbe73eea1a020103040c01406ad33ad9a30a160000051341917ba0080d2a17041602a0036f344c0556a0036f2a4a0d1602a0026f324c0556a0026f2a4a035616022a17051603a0046f324c051605a0046f2a4a03160516032a170616010c0d626c17070d16070d2b0e2c2a1708b41600ce01b1a006a0051604160516061404ffffffffa007160816080e2a62130b641601140101140480000000140400000001140400000001130564170a1709b47ad1a216000da3160440ce001609160ac1170b1604a108160b0201000203000fd3ed1a13f8160040cb17021602c51341947bb416010e0c1602010700000101001614010416000c0d62411341907b16000f0f16000d0f626d2a62a10006a0005e0000010100402f14010516000c0d62411341907b16000d132162a10116001321132962a10216001329133162a10316001331133962a104160013391340596271a105160013405913407962a106160013407913408362a10706a0005e0002010300402514010316000c0d62411341907b16000d132262631701160013221340426217021602132065424c05160113206513c3038d7ea4c68000ab1601160216001340421340626271ab01000004004049d307d5527db317000c0e1600d46590af8f17011816010004b3411341937b0c0d1600d4d0148c55140107411341907b0c0d1600d41de3aebe170216020c0d6217031603140102414c06140454686973140c544f20494d504c454d454e54411341a27b4a1a1603140103414c031602000a4a131603140104414c03160200084a0c1603140105414c03160200094a051603140101411341907b0c0c1600d4b573a9b4",
-  "codeHash": "a3fc9de272220d02f20a1e7158c869acaf75c14f4db645c9a3d73721700debf3",
+  "bytecode": "0b0c0e1c403940474062408a414c416d419241ee423b42e3010000000103d3facdcc89a00002010000000103d3926e1fc1a0010201000000070cd3fa5d1fcba001a0026fa0036fa0046fa005a006a00702010000000103d3d22165c1a0080201000102010bd3ecf235d916006bcb17011601c51341947b160102010001030112d30fec8d94160017010c0e160101011817021602a001410c0d1601d4fbe73eea1a020103040c01406ad33ad9a30a160000051341917ba0080d2a17041602a0036f344c0556a0036f2a4a0d1602a0026f324c0556a0026f2a4a035616022a17051603a0046f324c051605a0046f2a4a03160516032a170616010c0d626c17070d16070d2b0e2c2a1708b41600ce01b1a006a0051604160516061404ffffffffa007160816080e2a62130b641601140101140480000000140400000001140400000001130564170a1709b47ad1a216000da3160440ce001609160ac1170b1604a108160b0201000203000fd3ed1a13f816006bcb17021602c51341947bb416010e0c1602010700000101001614010416000c0d62411341907b16000f0f16000d0f626d2a62a10006a0005e0000010100402f14010516000c0d62411341907b16000d132162a10116001321132962a10216001329133162a10316001331133962a104160013391340596271a105160013405913407962a106160013407913408362a10706a0005e0002010300402514010316000c0d62411341907b16000d132262631701160013221340426217021602132065424c05160113206513c3038d7ea4c68000ab1601160216001340421340626271ab01000004004049d307d5527db317000c0e1600d46590af8f17011816010004b3411341937b0c0d1600d4d0148c55140107411341907b0c0d1600d41de3aebe170216020c0d6217031603140102414c06140454686973140c544f20494d504c454d454e54411341a27b4a1a1603140103414c031602000a4a131603140104414c03160200084a0c1603140105414c03160200094a051603140101411341907b0c0c1600d4b573a9b4",
+  "codeHash": "44443d439acd9891b3f8b98cc31173e3828fc89fd7a38c298c2798daa491866e",
   "fieldsSig": {
     "names": [
       "proposalTemplateId",
diff --git a/artifacts/DAOkitProposal.ral.json b/artifacts/DAOkitProposal.ral.json
index e6703dd..807b49c 100644
--- a/artifacts/DAOkitProposal.ral.json
+++ b/artifacts/DAOkitProposal.ral.json
@@ -1,8 +1,8 @@
 {
   "version": "v3.11.1",
   "name": "DAOkitProposal",
-  "bytecode": "100a101e402c408f40e840fe41164149421c4232010000000204d36590af8fce03ce0602010000000103d3d0148c55a00102010000000103d31de3aebea0000201030306004035d3e5372129160000091341917b1600ce01421341937b1600cb17031603c54c07160116020e0c1603d4ed37c7eb4a1cb11301641602160113026417051704b47ad1a2ce04ce05a31600ce0216041605c118a0046e0d2a68a10400081801000305004034d323723c7d16001601441602444e17030c0db3d47fff4ea11603411341907b16006c170416040c344c1316040e2e0c2f4c07a0026e0d2a68a1024a06a0036e0d2a68a10306a0046ea0026ea0036e60000818010301010107d3c9b5c135160000091341917b000802010000000008d3b573a9b4b3ce03411341937b140108a10101000202001bd34d603decb3ce03411600ce00451a1341937ba00114010141a0000c0d6216010c0d62411a1341907b1601a10000020003014073a0011700a001140101414c1bce0756324c14ce0a6d1343e8a0046e2cce096e2d324c021401044a01140103a101ce00ce010dab4a02a001024a403fa001140102414c03ce00b04a4038a001140104414c402fa0046e170116010e2d16010e2e2a0d2a1702ce0856324c0ba0026ea0036e334c021401054a01140106a1014a12a0026e1602344c03140105a1014a0aa0036e1602344c03140106a1014a02140104a1014a051341a2047c7b181600a001424c0da001140105414c06140107a1010c0cce03010b07a0015ea0010200000101010cb41600a50d2f16000d0dce0301051a02",
-  "codeHash": "42231ed9eb7694d753576752d0cb79863549e6dbfc2a0dc726bf3ec68decd48e",
+  "bytecode": "100a101e402c408f40e840fe4116414942134229010000000204d36590af8fce03ce0602010000000103d3d0148c55a00102010000000103d31de3aebea0000201030306004035d3e5372129160000091341917b1600ce01421341937b1600cb17031603c54c07160116020e0c1603d4ed37c7eb4a1cb11301641602160113026417051704b47ad1a2ce04ce05a31600ce0216041605c118a0046e0d2a68a10400081801000305004034d323723c7d16001601441602444e17030c0db3d47fff4ea11603411341907b16006c170416040c344c1316040e2e0c2f4c07a0026e0d2a68a1024a06a0036e0d2a68a10306a0046ea0026ea0036e60000818010301010107d3c9b5c135160000091341917b000802010000000008d3b573a9b4b3ce03411341937b140108a10101000202001bd34d603decb3ce03411600ce00451a1341937ba00114010141a0000c0d6216010c0d62411a1341907b1601a1000002000201406da0011700a001140101414c1bce0756324c14ce0a6d1343e8a0046e2cce096e2d324c021401044a01140103a101ce00ce010dab4a02a001024a4039a001140102414c03ce00b04a4032a001140104414c4029a0046e0e2d0d2a1701ce0856324c0ba0026ea0036e334c021401054a01140106a1014a12a0026e1601344c03140105a1014a0aa0036e1601344c03140106a1014a02140104a1014a051341a2047c7b181600a001424c0da001140105414c06140107a1010c0cce03010b07a0015ea0010200000101010cb41600a50d2f16000d0dce0301051a02",
+  "codeHash": "57d32f70aa33e4db44e99e03a7defe1d7078479cda98eb7fbbd92de9c9cb9539",
   "fieldsSig": {
     "names": [
       "authorAddress",
diff --git a/artifacts/ts/DAOkitOrg.ts b/artifacts/ts/DAOkitOrg.ts
index ca4b182..edeb3fa 100644
--- a/artifacts/ts/DAOkitOrg.ts
+++ b/artifacts/ts/DAOkitOrg.ts
@@ -378,7 +378,7 @@ export const DAOkitOrg = new Factory(
   Contract.fromJson(
     DAOkitOrgContractJson,
     "",
-    "a3fc9de272220d02f20a1e7158c869acaf75c14f4db645c9a3d73721700debf3",
+    "44443d439acd9891b3f8b98cc31173e3828fc89fd7a38c298c2798daa491866e",
     []
   )
 );
diff --git a/artifacts/ts/DAOkitProposal.ts b/artifacts/ts/DAOkitProposal.ts
index 65c69db..0314209 100644
--- a/artifacts/ts/DAOkitProposal.ts
+++ b/artifacts/ts/DAOkitProposal.ts
@@ -352,7 +352,7 @@ export const DAOkitProposal = new Factory(
   Contract.fromJson(
     DAOkitProposalContractJson,
     "",
-    "42231ed9eb7694d753576752d0cb79863549e6dbfc2a0dc726bf3ec68decd48e",
+    "57d32f70aa33e4db44e99e03a7defe1d7078479cda98eb7fbbd92de9c9cb9539",
     []
   )
 );
diff --git a/contracts/organization.ral b/contracts/organization.ral
index 55f57bd..c95af07 100644
--- a/contracts/organization.ral
+++ b/contracts/organization.ral
@@ -53,7 +53,7 @@ Contract DAOkitOrg(
 	}
 
 	pub fn proposalByIndex(index: U256) -> IDAOkitProposal {
-		let proposalId = subContractId!(toByteVec!(index))
+		let proposalId = subContractId!(u256To32Byte!(index))
 		assert!(contractExists!(proposalId), ErrorCodes.NotFound)
 		return IDAOkitProposal(proposalId)
 	}
@@ -111,7 +111,7 @@ Contract DAOkitOrg(
 
 	@using(checkExternalCaller = false)
 	pub fn editProposal(proposalIndex: U256, newPayload: ByteVec) -> () {
-		let proposalId = subContractId!(toByteVec!(proposalIndex))
+		let proposalId = subContractId!(u256To32Byte!(proposalIndex))
 		assert!(contractExists!(proposalId), ErrorCodes.NotFound)
 		DAOkitProposal(proposalId).setPayload(callerAddress!(), newPayload)
 	}
-- 
GitLab


From 70bc49aa479a59be720fb641e16fc84f7846c92b Mon Sep 17 00:00:00 2001
From: Fabio Bonfiglio <fabio.bonfiglio@protonmail.ch>
Date: Wed, 26 Feb 2025 14:28:58 +0100
Subject: [PATCH 32/40] Adapt tests to new voting count model and add tests
 with org contract for enacting

---
 test/proposal.test.ts | 168 +++++++++++++++++++++++++++++++++++-------
 1 file changed, 140 insertions(+), 28 deletions(-)

diff --git a/test/proposal.test.ts b/test/proposal.test.ts
index 1b999bc..52e630a 100644
--- a/test/proposal.test.ts
+++ b/test/proposal.test.ts
@@ -1,6 +1,6 @@
-import { addressFromContractId, MINIMAL_CONTRACT_DEPOSIT, stringToHex, web3 } from "@alephium/web3";
+import { addressFromContractId, MINIMAL_CONTRACT_DEPOSIT, stringToHex, subContractId, web3 } from "@alephium/web3";
 import { randomContractId, testAddress } from "@alephium/web3-test";
-import { DAOkitProposal } from "../artifacts/ts";
+import { DAOkitOrg, DAOkitProposal } from "../artifacts/ts";
 import { uintToByteVec, byteVecToUint } from "../src/lib/ralph-helpers";
 
 const TEST_METADATA_URI = "https://bafybeicfesp47qc6s5yoryps3osvv7fljdlrpk75wf2tyi6w2cjfj2alzi.ipfs.dweb.link/mdata.json";
@@ -8,13 +8,13 @@ const TEST_METADATA_URI = "https://bafybeicfesp47qc6s5yoryps3osvv7fljdlrpk75wf2t
 jest.setTimeout(process.env.CI ? 2000 : 400);
 
 describe("DAOkitProposal unit tests", () => {
-	const testOrgId = randomContractId();
-	const testProposalId = randomContractId();
-	const testBallotTemplate = randomContractId();
-	const authorENFTid = randomContractId();
-	const commitmentTokenId = randomContractId();
-	const commitmentDepositAmount = 20n * 10n**18n;
+	const testOrgId = randomContractId(0);
 	const testProposalIndex = 12n;
+	const testProposalId = subContractId(testOrgId, uintToByteVec(testProposalIndex, 32), 0);
+	const testBallotTemplate = randomContractId(0);
+	const authorENFTid = randomContractId(0);
+	const commitmentTokenId = randomContractId(0);
+	const commitmentDepositAmount = 20n * 10n**18n;
 	const ONE_DAY = 24000n * 3600n;
 	const FOUR_HOURS = 4000n * 3600n;
 	const testCommitPeriodEnd = BigInt(Date.now()) + ONE_DAY;
@@ -51,8 +51,8 @@ describe("DAOkitProposal unit tests", () => {
 				requiredQuorum,
 				payload: GENERIC_MOTION_PAYLOAD,
 				state,
-				voteDiff: "80000000",
-				votedCount: "00000000",
+				acceptedCount: "00000001",
+				rejectedCount: "00000000",
 				committedCount: "00000000"
 			}
 		});
@@ -86,8 +86,8 @@ describe("DAOkitProposal unit tests", () => {
 				requiredQuorum,
 				payload: GENERIC_MOTION_PAYLOAD,
 				state,
-				voteDiff: "80000000",
-				votedCount: "00000000",
+				acceptedCount: "00000001",
+				rejectedCount: "00000000",
 				committedCount: "00000000"
 			}
 		});
@@ -118,8 +118,8 @@ describe("DAOkitProposal unit tests", () => {
 				requiredQuorum,
 				payload: GENERIC_MOTION_PAYLOAD,
 				state,
-				voteDiff: "80000000",
-				votedCount: "00000000",
+				acceptedCount: "00000001",
+				rejectedCount: "00000000",
 				committedCount: "00000000"
 			}
 		});
@@ -159,8 +159,8 @@ describe("DAOkitProposal unit tests", () => {
 				requiredQuorum,
 				payload: GENERIC_MOTION_PAYLOAD,
 				state,
-				voteDiff: "80000000",
-				votedCount: "00000000",
+				acceptedCount: "00000001",
+				rejectedCount: "00000000",
 				committedCount
 			}
 		});
@@ -207,8 +207,8 @@ describe("DAOkitProposal unit tests", () => {
 				requiredQuorum,
 				payload: GENERIC_MOTION_PAYLOAD,
 				state,
-				voteDiff: "80000000",
-				votedCount: "00000000",
+				acceptedCount: "00000001",
+				rejectedCount: "00000000",
 				committedCount
 			}
 		});
@@ -220,15 +220,38 @@ describe("DAOkitProposal unit tests", () => {
 		expect(testResult.events[0].fields.updatedState).toEqual(EXPECTED_STATE);
 	});
 
-	it("Should change state from Voting to Rejected if majority not reached when voting period ends", async () => {
-		const maxVoters = uintToByteVec(testMaxVoters, 4);
-		const requiredQuorum = uintToByteVec(Math.round(10 * testRequiredQuorums), 2);
-		const committedCount = uintToByteVec(Math.round((testRequiredQuorums + 4.2) * testMaxVoters / 100), 4);
-		const votedCount = uintToByteVec(byteVecToUint(committedCount) - 2n, 4);
-		const voteDiff = uintToByteVec(2147000000n, 4);
+	test.each([
+		[11, 0, 0],
+		[11, 4, 4],
+		[11, 5, 5],
+		[11, 5, 6],
+		[12, 5, 5],
+		[12, 5, 6],
+		[12, 6, 6],
+		[127, 32, 32],
+		[127, 32, 33],
+		[127, 54, 54],
+		[127, 54, 55],
+		[127, 63, 63],
+		[127, 63, 64],
+		[8399, 1, 1],
+		[8399, 84, 84],
+		[8399, 84, 85],
+		[8399, 85, 85],
+		[8399, 842, 842],
+		[8399, 842, 843],
+		[8399, 0, 2400],
+		[8399, 4199, 4199],
+		[8399, 4199, 4200]
+	])("Should reject proposal if majority not reached (%p participants, %p accepted and %p rejected) when voting period ends", async (participants: number, accepts: number, rejects: number) => {
+		const requiredQuorum = uintToByteVec(200n, 2);
+		const maxVoters = uintToByteVec(Math.floor(participants / 0.2) - participants, 4);
+		const committedCount = uintToByteVec(participants, 4);
 		const state = "04"; // Voting
+		const acceptedCount = uintToByteVec(accepts, 4);
+		const rejectedCount = uintToByteVec(rejects, 4);
 		const commitmentPeriodEnd = BigInt(Date.now()) - FOUR_HOURS;
-		const votingPeriodEnd = commitmentPeriodEnd + 3600000n;
+		const votingPeriodEnd = commitmentPeriodEnd + 3600000n; // Voting ended 3 hours ago
 		const EXPECTED_STATE = "06"; // Rejected
 		const testResult = await DAOkitProposal.tests.updateState({
 			address: addressFromContractId(testProposalId),
@@ -255,8 +278,8 @@ describe("DAOkitProposal unit tests", () => {
 				requiredQuorum,
 				payload: GENERIC_MOTION_PAYLOAD,
 				state,
-				voteDiff,
-				votedCount,
+				acceptedCount,
+				rejectedCount,
 				committedCount
 			}
 		});
@@ -268,7 +291,96 @@ describe("DAOkitProposal unit tests", () => {
 		expect(testResult.events[0].fields.updatedState).toEqual(EXPECTED_STATE);
 	});
 
-	it.todo("Should change state from Voting to Accepted → Executing when majority is reached");
+	test.each([
+		[11, 1, 0],
+		[11, 5, 4],
+		[11, 6, 5],
+		[12, 6, 5],
+		[127, 32, 31],
+		[127, 33, 32],
+		[127, 55, 54],
+		[127, 63, 62],
+		[127, 64, 63],
+		[8399, 1, 0],
+		[8399, 84, 83],
+		[8399, 85, 84],
+		[8399, 842, 841],
+		[8399, 843, 842],
+		[8399, 2400, 0],
+		[8399, 4200, 4199]
+	])("Should execute proposal if majority is reached (%p participants, %p accepted and %p rejected) when voting period ends", async (participants: number, accepts: number, rejects: number) => {
+		const requiredQuorum = uintToByteVec(200n, 2);
+		const maxVoters = uintToByteVec(Math.floor(participants / 0.2) - participants, 4);
+		const committedCount = uintToByteVec(participants, 4);
+		const state = "04"; // Voting
+		const acceptedCount = uintToByteVec(accepts, 4);
+		const rejectedCount = uintToByteVec(rejects, 4);
+		const commitmentPeriodEnd = BigInt(Date.now()) - FOUR_HOURS;
+		const votingPeriodEnd = commitmentPeriodEnd + 3600000n; // Voting ended 3 hours ago
+		const EXPECTED_STATE = "08"; // Executing
+		const testResult = await DAOkitProposal.tests.updateState({
+			address: addressFromContractId(testProposalId),
+			group: 0,
+			initialAsset: {
+				alphAmount: MINIMAL_CONTRACT_DEPOSIT,
+				tokens: [
+					{
+						id: authorENFTid,
+						amount: 1n
+					}
+				]
+			},
+			initialFields: {
+				authorAddress: addressFromContractId(testProposalId),
+				authorENFTid,
+				ballotTemplateId: testBallotTemplate,
+				organizationId: testOrgId,
+				commitmentDepositTokenId: commitmentTokenId,
+				commitmentDepositAmount,
+				proposalIndex: testProposalIndex,
+				commitmentPeriodEnd,
+				votingPeriodEnd,
+				maxVoters,
+				requiredQuorum,
+				payload: GENERIC_MOTION_PAYLOAD,
+				state,
+				acceptedCount,
+				rejectedCount,
+				committedCount
+			},
+			existingContracts: [
+				{
+					address: addressFromContractId(testOrgId),
+					asset: {
+						alphAmount: MINIMAL_CONTRACT_DEPOSIT,
+					},
+					contractId: testOrgId,
+					bytecode: DAOkitOrg.contract.bytecode,
+					codeHash: DAOkitOrg.contract.codeHash,
+					fields: {
+						proposalTemplateId: randomContractId(0),
+						ballotTemplateId: testBallotTemplate,
+						organizationUri: stringToHex("https://dao.example.com/"),
+						membershipCollectionId: randomContractId(0),
+						minimalCommitmentPeriod: uintToByteVec(ONE_DAY, 8),
+						maximalCommitmentPeriod: uintToByteVec(ONE_DAY * 30n, 8),
+						minimalVotingPeriod: uintToByteVec(ONE_DAY, 8),
+						commitmentDepositAmount: 1n,
+						commitmentDepositTokenId: randomContractId(0),
+						requiredQuorums: uintToByteVec(200n, 2).repeat(5),
+						currentProposalIndex: testProposalIndex
+					},
+					fieldsSig: DAOkitOrg.contract.fieldsSig
+				}
+			]
+		});
+		expect(testResult.returns).toEqual(EXPECTED_STATE);
+		expect(testResult.contracts[1].fields.state).toEqual(EXPECTED_STATE);
+		expect(testResult.events.length).toEqual(1);
+		expect(testResult.events[0].contractAddress).toEqual(addressFromContractId(testProposalId));
+		expect(testResult.events[0].name).toEqual("ProposalStateChanged");
+		expect(testResult.events[0].fields.updatedState).toEqual(EXPECTED_STATE);
+	});
 
 	it.todo("Shouldn't change state when in Proposed and commitment period not ended");
 
-- 
GitLab


From e88325baed8f95daa954511e654ff3330d6846cf Mon Sep 17 00:00:00 2001
From: Fabio Bonfiglio <fabio.bonfiglio@protonmail.ch>
Date: Wed, 26 Feb 2025 17:19:50 +0100
Subject: [PATCH 33/40] Add absolute majority instant rejection test

---
 test/proposal.test.ts | 70 ++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 69 insertions(+), 1 deletion(-)

diff --git a/test/proposal.test.ts b/test/proposal.test.ts
index 52e630a..1cefa5b 100644
--- a/test/proposal.test.ts
+++ b/test/proposal.test.ts
@@ -317,7 +317,7 @@ describe("DAOkitProposal unit tests", () => {
 		const rejectedCount = uintToByteVec(rejects, 4);
 		const commitmentPeriodEnd = BigInt(Date.now()) - FOUR_HOURS;
 		const votingPeriodEnd = commitmentPeriodEnd + 3600000n; // Voting ended 3 hours ago
-		const EXPECTED_STATE = "08"; // Executing
+		const EXPECTED_STATE = "08"; // Enacted
 		const testResult = await DAOkitProposal.tests.updateState({
 			address: addressFromContractId(testProposalId),
 			group: 0,
@@ -382,6 +382,74 @@ describe("DAOkitProposal unit tests", () => {
 		expect(testResult.events[0].fields.updatedState).toEqual(EXPECTED_STATE);
 	});
 
+	test.each([
+		[11, 0, 6],
+		[11, 1, 6],
+		[11, 4, 6],
+		[11, 5, 6],
+		[12, 5, 7],
+		[12, 6, 7],
+		[127, 0, 64],
+		[127, 1, 64],
+		[127, 32, 64],
+		[127, 62, 64],
+		[127, 63, 64],
+		[8399, 1, 4200],
+		[8399, 84, 4200],
+		[8399, 85, 4200],
+		[8399, 842, 4200],
+		[8399, 843, 4200],
+		[8399, 0, 4200],
+		[8399, 4198, 4200],
+		[8399, 4199, 4200]
+	])("Should immediately reject proposal if absolute majority is reached (%p participants, %p accepted and %p rejected) before voting period ends", async (participants: number, accepts: number, rejects: number) => {
+		const requiredQuorum = uintToByteVec(200n, 2);
+		const maxVoters = uintToByteVec(Math.floor(participants / 0.2) - participants, 4);
+		const committedCount = uintToByteVec(participants, 4);
+		const state = "04"; // Voting
+		const acceptedCount = uintToByteVec(accepts, 4);
+		const rejectedCount = uintToByteVec(rejects, 4);
+		const commitmentPeriodEnd = BigInt(Date.now()) - FOUR_HOURS;
+		const votingPeriodEnd = commitmentPeriodEnd + ONE_DAY; // Voting period ends in 20 hours
+		const EXPECTED_STATE = "06"; // Rejected
+		const testResult = await DAOkitProposal.tests.updateState({
+			address: addressFromContractId(testProposalId),
+			initialAsset: {
+				alphAmount: MINIMAL_CONTRACT_DEPOSIT,
+				tokens: [
+					{
+						id: authorENFTid,
+						amount: 1n
+					}
+				]
+			},
+			initialFields: {
+				authorAddress: addressFromContractId(testProposalId),
+				authorENFTid,
+				ballotTemplateId: testBallotTemplate,
+				organizationId: testOrgId,
+				commitmentDepositTokenId: commitmentTokenId,
+				commitmentDepositAmount,
+				proposalIndex: testProposalIndex,
+				commitmentPeriodEnd,
+				votingPeriodEnd,
+				maxVoters,
+				requiredQuorum,
+				payload: GENERIC_MOTION_PAYLOAD,
+				state,
+				acceptedCount,
+				rejectedCount,
+				committedCount
+			}
+		});
+		expect(testResult.returns).toEqual(EXPECTED_STATE);
+		expect(testResult.contracts[0].fields.state).toEqual(EXPECTED_STATE);
+		expect(testResult.events.length).toEqual(1);
+		expect(testResult.events[0].contractAddress).toEqual(addressFromContractId(testProposalId));
+		expect(testResult.events[0].name).toEqual("ProposalStateChanged");
+		expect(testResult.events[0].fields.updatedState).toEqual(EXPECTED_STATE);
+	});
+
 	it.todo("Shouldn't change state when in Proposed and commitment period not ended");
 
 	it.todo("Shouldn't change state when in Voting and conditions (no majority reached and period not ended) for closing vote aren't met");
-- 
GitLab


From 3b64a57c8e8e08088a5613704cea5777b28a6d77 Mon Sep 17 00:00:00 2001
From: Fabio Bonfiglio <fabio.bonfiglio@protonmail.ch>
Date: Wed, 26 Feb 2025 17:44:01 +0100
Subject: [PATCH 34/40] Add absolute majority instant enacting test

---
 test/proposal.test.ts | 101 +++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 100 insertions(+), 1 deletion(-)

diff --git a/test/proposal.test.ts b/test/proposal.test.ts
index 1cefa5b..10443bd 100644
--- a/test/proposal.test.ts
+++ b/test/proposal.test.ts
@@ -394,6 +394,9 @@ describe("DAOkitProposal unit tests", () => {
 		[127, 32, 64],
 		[127, 62, 64],
 		[127, 63, 64],
+		[128, 0, 65],
+		[128, 32, 65],
+		[128, 63, 65],
 		[8399, 1, 4200],
 		[8399, 84, 4200],
 		[8399, 85, 4200],
@@ -401,7 +404,8 @@ describe("DAOkitProposal unit tests", () => {
 		[8399, 843, 4200],
 		[8399, 0, 4200],
 		[8399, 4198, 4200],
-		[8399, 4199, 4200]
+		[8399, 4199, 4200],
+		[8400, 4199, 4201]
 	])("Should immediately reject proposal if absolute majority is reached (%p participants, %p accepted and %p rejected) before voting period ends", async (participants: number, accepts: number, rejects: number) => {
 		const requiredQuorum = uintToByteVec(200n, 2);
 		const maxVoters = uintToByteVec(Math.floor(participants / 0.2) - participants, 4);
@@ -450,6 +454,101 @@ describe("DAOkitProposal unit tests", () => {
 		expect(testResult.events[0].fields.updatedState).toEqual(EXPECTED_STATE);
 	});
 
+	test.each([
+		[11, 6, 0],
+		[11, 6, 1],
+		[11, 6, 5],
+		[12, 7, 5],
+		[127, 64, 0],
+		[127, 64, 1],
+		[127, 64, 54],
+		[127, 64, 62],
+		[127, 64, 63],
+		[128, 65, 0],
+		[128, 65, 32],
+		[128, 65, 63],
+		[8399, 4200, 0],
+		[8399, 4200, 83],
+		[8399, 4200, 84],
+		[8399, 4200, 841],
+		[8399, 4200, 842],
+		[8399, 4200, 1],
+		[8399, 4200, 4199],
+		[8400, 4201, 4199]
+	])("Should immediately execute proposal if absolute majority is reached (%p participants, %p accepted and %p rejected) before voting period ends", async (participants: number, accepts: number, rejects: number) => {
+		const requiredQuorum = uintToByteVec(200n, 2);
+		const maxVoters = uintToByteVec(Math.floor(participants / 0.2) - participants, 4);
+		const committedCount = uintToByteVec(participants, 4);
+		const state = "04"; // Voting
+		const acceptedCount = uintToByteVec(accepts, 4);
+		const rejectedCount = uintToByteVec(rejects, 4);
+		const commitmentPeriodEnd = BigInt(Date.now()) - FOUR_HOURS;
+		const votingPeriodEnd = commitmentPeriodEnd + ONE_DAY; // Voting period ends in 20 hours
+		const EXPECTED_STATE = "08"; // Enacted
+		const testResult = await DAOkitProposal.tests.updateState({
+			address: addressFromContractId(testProposalId),
+			group: 0,
+			initialAsset: {
+				alphAmount: MINIMAL_CONTRACT_DEPOSIT,
+				tokens: [
+					{
+						id: authorENFTid,
+						amount: 1n
+					}
+				]
+			},
+			initialFields: {
+				authorAddress: addressFromContractId(testProposalId),
+				authorENFTid,
+				ballotTemplateId: testBallotTemplate,
+				organizationId: testOrgId,
+				commitmentDepositTokenId: commitmentTokenId,
+				commitmentDepositAmount,
+				proposalIndex: testProposalIndex,
+				commitmentPeriodEnd,
+				votingPeriodEnd,
+				maxVoters,
+				requiredQuorum,
+				payload: GENERIC_MOTION_PAYLOAD,
+				state,
+				acceptedCount,
+				rejectedCount,
+				committedCount
+			},
+			existingContracts: [
+				{
+					address: addressFromContractId(testOrgId),
+					asset: {
+						alphAmount: MINIMAL_CONTRACT_DEPOSIT,
+					},
+					contractId: testOrgId,
+					bytecode: DAOkitOrg.contract.bytecode,
+					codeHash: DAOkitOrg.contract.codeHash,
+					fields: {
+						proposalTemplateId: randomContractId(0),
+						ballotTemplateId: testBallotTemplate,
+						organizationUri: stringToHex("https://dao.example.com/"),
+						membershipCollectionId: randomContractId(0),
+						minimalCommitmentPeriod: uintToByteVec(ONE_DAY, 8),
+						maximalCommitmentPeriod: uintToByteVec(ONE_DAY * 30n, 8),
+						minimalVotingPeriod: uintToByteVec(ONE_DAY, 8),
+						commitmentDepositAmount: 1n,
+						commitmentDepositTokenId: randomContractId(0),
+						requiredQuorums: uintToByteVec(200n, 2).repeat(5),
+						currentProposalIndex: testProposalIndex
+					},
+					fieldsSig: DAOkitOrg.contract.fieldsSig
+				}
+			]
+		});
+		expect(testResult.returns).toEqual(EXPECTED_STATE);
+		expect(testResult.contracts[1].fields.state).toEqual(EXPECTED_STATE);
+		expect(testResult.events.length).toEqual(1);
+		expect(testResult.events[0].contractAddress).toEqual(addressFromContractId(testProposalId));
+		expect(testResult.events[0].name).toEqual("ProposalStateChanged");
+		expect(testResult.events[0].fields.updatedState).toEqual(EXPECTED_STATE);
+	});
+
 	it.todo("Shouldn't change state when in Proposed and commitment period not ended");
 
 	it.todo("Shouldn't change state when in Voting and conditions (no majority reached and period not ended) for closing vote aren't met");
-- 
GitLab


From 5a58f789a1b43c084af0744d96a308297b1d13e1 Mon Sep 17 00:00:00 2001
From: Fabio Bonfiglio <fabio.bonfiglio@protonmail.ch>
Date: Wed, 26 Feb 2025 19:10:48 +0100
Subject: [PATCH 35/40] Add no state change tests

---
 test/proposal.test.ts | 115 +++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 113 insertions(+), 2 deletions(-)

diff --git a/test/proposal.test.ts b/test/proposal.test.ts
index 10443bd..d780e7c 100644
--- a/test/proposal.test.ts
+++ b/test/proposal.test.ts
@@ -549,7 +549,118 @@ describe("DAOkitProposal unit tests", () => {
 		expect(testResult.events[0].fields.updatedState).toEqual(EXPECTED_STATE);
 	});
 
-	it.todo("Shouldn't change state when in Proposed and commitment period not ended");
+	it("Shouldn't change state when in Proposed and commitment period not ended", async () => {
+		const maxVoters = uintToByteVec(testMaxVoters, 4);
+		const requiredQuorum = uintToByteVec(Math.round(10 * testRequiredQuorums), 2);
+		const committedCount = uintToByteVec(Math.round((testRequiredQuorums + 4.2) * testMaxVoters / 100), 4);
+		const state = "01"; // Proposed
+		const commitmentPeriodEnd = BigInt(Date.now()) + FOUR_HOURS; // Commitment period ends in four hours
+		const votingPeriodEnd = commitmentPeriodEnd + ONE_DAY;
+		const EXPECTED_STATE = state; // Still Proposed !
+		const testResult = await DAOkitProposal.tests.updateState({
+			address: addressFromContractId(testProposalId),
+			initialAsset: {
+				alphAmount: MINIMAL_CONTRACT_DEPOSIT,
+				tokens: [
+					{
+						id: authorENFTid,
+						amount: 1n
+					}
+				]
+			},
+			initialFields: {
+				authorAddress: addressFromContractId(testProposalId),
+				authorENFTid,
+				ballotTemplateId: testBallotTemplate,
+				organizationId: testOrgId,
+				commitmentDepositTokenId: commitmentTokenId,
+				commitmentDepositAmount,
+				proposalIndex: testProposalIndex,
+				commitmentPeriodEnd,
+				votingPeriodEnd,
+				maxVoters,
+				requiredQuorum,
+				payload: GENERIC_MOTION_PAYLOAD,
+				state,
+				acceptedCount: "00000001",
+				rejectedCount: "00000000",
+				committedCount
+			}
+		});
+		expect(testResult.returns).toEqual(EXPECTED_STATE);
+		expect(testResult.contracts[0].fields.state).toEqual(EXPECTED_STATE);
+		expect(testResult.events.length).toEqual(0);
+	});
 
-	it.todo("Shouldn't change state when in Voting and conditions (no majority reached and period not ended) for closing vote aren't met");
+	test.each([
+		[11, 0, 5],
+		[11, 5, 0],
+		[11, 5, 1],
+		[11, 4, 5],
+		[11, 5, 4],
+		[12, 5, 6],
+		[12, 6, 5],
+		[127, 0, 63],
+		[127, 63, 1],
+		[127, 32, 63],
+		[127, 62, 63],
+		[127, 63, 62],
+		[128, 0, 64],
+		[128, 32, 64],
+		[128, 64, 63],
+		[8399, 1, 4199],
+		[8399, 84, 4199],
+		[8399, 4199, 84],
+		[8399, 842, 4199],
+		[8399, 4199, 843],
+		[8399, 0, 4199],
+		[8399, 4199, 4198],
+		[8399, 4199, 4199],
+		[8400, 4199, 4200],
+		[8400, 4200, 4199]
+	])("Shouldn't change state when in Voting and conditions (%p participants, %p accepts and %p rejects) for closing vote aren't met and period not ended", async (participants: number, accepts: number, rejects: number) => {
+		const requiredQuorum = uintToByteVec(200n, 2);
+		const maxVoters = uintToByteVec(Math.floor(participants / 0.2) - participants, 4);
+		const committedCount = uintToByteVec(participants, 4);
+		const state = "04"; // Voting
+		const acceptedCount = uintToByteVec(accepts, 4);
+		const rejectedCount = uintToByteVec(rejects, 4);
+		const commitmentPeriodEnd = BigInt(Date.now()) - FOUR_HOURS;
+		const votingPeriodEnd = commitmentPeriodEnd + ONE_DAY; // Voting period ends in 20 hours
+		const EXPECTED_STATE = state; // Still Voting !
+		const testResult = await DAOkitProposal.tests.updateState({
+			address: addressFromContractId(testProposalId),
+			group: 0,
+			initialAsset: {
+				alphAmount: MINIMAL_CONTRACT_DEPOSIT,
+				tokens: [
+					{
+						id: authorENFTid,
+						amount: 1n
+					}
+				]
+			},
+			initialFields: {
+				authorAddress: addressFromContractId(testProposalId),
+				authorENFTid,
+				ballotTemplateId: testBallotTemplate,
+				organizationId: testOrgId,
+				commitmentDepositTokenId: commitmentTokenId,
+				commitmentDepositAmount,
+				proposalIndex: testProposalIndex,
+				commitmentPeriodEnd,
+				votingPeriodEnd,
+				maxVoters,
+				requiredQuorum,
+				payload: GENERIC_MOTION_PAYLOAD,
+				state,
+				acceptedCount,
+				rejectedCount,
+				committedCount
+			}
+		});
+		expect(testResult.returns).toEqual(EXPECTED_STATE);
+		expect(testResult.contracts[0].fields.state).toEqual(EXPECTED_STATE);
+		expect(testResult.events.length).toEqual(0);
+	});
 });
-- 
GitLab


From b33fbb971724de722f64ae36e617c301d16b9f21 Mon Sep 17 00:00:00 2001
From: Fabio Bonfiglio <fabio.bonfiglio@protonmail.ch>
Date: Wed, 26 Feb 2025 21:42:26 +0100
Subject: [PATCH 36/40] Add test file for DAOkitVotingBallot contract

---
 test/ballot.test.ts | 5 +++++
 1 file changed, 5 insertions(+)
 create mode 100644 test/ballot.test.ts

diff --git a/test/ballot.test.ts b/test/ballot.test.ts
new file mode 100644
index 0000000..973936c
--- /dev/null
+++ b/test/ballot.test.ts
@@ -0,0 +1,5 @@
+import { MINIMAL_CONTRACT_DEPOSIT } from "@alephium/web3";
+import { randomContractId } from "@alephium/web3-test";
+import { DAOkitProposal, DAOkitVotingBallot } from "../artifacts/ts";
+
+// TODO: implement unit tests for ballot contract
\ No newline at end of file
-- 
GitLab


From 433ef23d6ade64a3eef593e0db3fa047916d5a8a Mon Sep 17 00:00:00 2001
From: Fabio Bonfiglio <fabio.bonfiglio@protonmail.ch>
Date: Wed, 26 Feb 2025 21:48:38 +0100
Subject: [PATCH 37/40] Adapt revealing scheme for more versatility

---
 .project.json                               | 14 ++++++------
 artifacts/DAOkitProposal.ral.json           |  9 +++-----
 artifacts/DAOkitVotingBallot.ral.json       | 17 ++++++---------
 artifacts/ts/DAOkitProposal.ts              | 12 +++--------
 artifacts/ts/DAOkitVotingBallot.ts          | 24 +++++++++------------
 contracts/ballot.ral                        |  9 ++++----
 contracts/interfaces/ballot_interface.ral   |  4 ++--
 contracts/interfaces/proposal_interface.ral |  2 +-
 contracts/proposal.ral                      | 19 +++++++++-------
 9 files changed, 48 insertions(+), 62 deletions(-)

diff --git a/.project.json b/.project.json
index 9de3e82..f46fd56 100644
--- a/.project.json
+++ b/.project.json
@@ -19,15 +19,15 @@
     },
     "DAOkitProposal": {
       "sourceFile": "proposal.ral",
-      "sourceCodeHash": "20173d572dfa8106ca569868bb8e8d3ffef2da8e917543fb4adf7d8ab67b8466",
-      "bytecodeDebugPatch": "",
-      "codeHashDebug": "57d32f70aa33e4db44e99e03a7defe1d7078479cda98eb7fbbd92de9c9cb9539"
+      "sourceCodeHash": "cde38a842acbc4c7d10014fc27ffdd0a4d66e7fd9b275d6f023e9f6017c9b7bf",
+      "bytecodeDebugPatch": "=18-2+2b=2-2=2-2=2-2+59=1-2+18=3+56=2+6c=327-1+b=86-2+4020=35-1+d=32+0216027e024022426c616e6b20766f74652076616c6964617465642066726f6d206164647265737320=672",
+      "codeHashDebug": "60d3e6ae2c83db135852d36f3b29fcb41df693d53fd46fd48931f1430793e1ef"
     },
     "DAOkitVotingBallot": {
       "sourceFile": "ballot.ral",
-      "sourceCodeHash": "f3c4ab798847a38005005890941631cd44bf0e9b1b934a4369d91e3b098fe2f8",
+      "sourceCodeHash": "076dc5febcc5964a9dd83fdf149277ea89b560b283717c091502a67a899ee022",
       "bytecodeDebugPatch": "",
-      "codeHashDebug": "fd07f2a8171749c8414517e0ae1ac964e3c413ad23832e26abfd0f331ba3c5b6"
+      "codeHashDebug": "8d50fb7af4255e3169da9a813756b14e95fc299793a8aad35e8514c7ef00ac66"
     },
     "IDAOkitOrg": {
       "sourceFile": "interfaces/organization_interface.ral",
@@ -37,13 +37,13 @@
     },
     "IDAOkitProposal": {
       "sourceFile": "interfaces/proposal_interface.ral",
-      "sourceCodeHash": "215ce8e11988504264c21a6cdfbe8a076bf80ddac3d0eadcb40d27b839dc8875",
+      "sourceCodeHash": "bc48e3a394f80edfb60bfcb951b227f2f250e4bff61d2766ea1a912a65b06083",
       "bytecodeDebugPatch": "",
       "codeHashDebug": ""
     },
     "IDAOkitVotingBallot": {
       "sourceFile": "interfaces/ballot_interface.ral",
-      "sourceCodeHash": "e22b472787583b70532baa83291b0b50d3eadf898b6ce40a46ae0bc03c96366c",
+      "sourceCodeHash": "0f4da80f119263253e49e6f28c6e462fc30866207fa65b95743cad32c46b3ec7",
       "bytecodeDebugPatch": "",
       "codeHashDebug": ""
     },
diff --git a/artifacts/DAOkitProposal.ral.json b/artifacts/DAOkitProposal.ral.json
index 807b49c..6ba4cd0 100644
--- a/artifacts/DAOkitProposal.ral.json
+++ b/artifacts/DAOkitProposal.ral.json
@@ -1,8 +1,8 @@
 {
   "version": "v3.11.1",
   "name": "DAOkitProposal",
-  "bytecode": "100a101e402c408f40e840fe4116414942134229010000000204d36590af8fce03ce0602010000000103d3d0148c55a00102010000000103d31de3aebea0000201030306004035d3e5372129160000091341917b1600ce01421341937b1600cb17031603c54c07160116020e0c1603d4ed37c7eb4a1cb11301641602160113026417051704b47ad1a2ce04ce05a31600ce0216041605c118a0046e0d2a68a10400081801000305004034d323723c7d16001601441602444e17030c0db3d47fff4ea11603411341907b16006c170416040c344c1316040e2e0c2f4c07a0026e0d2a68a1024a06a0036e0d2a68a10306a0046ea0026ea0036e60000818010301010107d3c9b5c135160000091341917b000802010000000008d3b573a9b4b3ce03411341937b140108a10101000202001bd34d603decb3ce03411600ce00451a1341937ba00114010141a0000c0d6216010c0d62411a1341907b1601a1000002000201406da0011700a001140101414c1bce0756324c14ce0a6d1343e8a0046e2cce096e2d324c021401044a01140103a101ce00ce010dab4a02a001024a4039a001140102414c03ce00b04a4032a001140104414c4029a0046e0e2d0d2a1701ce0856324c0ba0026ea0036e334c021401054a01140106a1014a12a0026e1601344c03140105a1014a0aa0036e1601344c03140106a1014a02140104a1014a051341a2047c7b181600a001424c0da001140105414c06140107a1010c0cce03010b07a0015ea0010200000101010cb41600a50d2f16000d0dce0301051a02",
-  "codeHash": "57d32f70aa33e4db44e99e03a7defe1d7078479cda98eb7fbbd92de9c9cb9539",
+  "bytecode": "100a101e402c409d41014117412f4162422c4242010000000204d36590af8fce03ce0602010000000103d3d0148c55a00102010000000103d31de3aebea000020103020500403cd3a5776feb160000091341917b1600ce01421341937b1600cb17021602c5a001140101421a4c0616010d0c1602d4f66674ad4a4020a001140101411341907bb1130164160113016417041703b47ad1a2ce04ce05a31600ce0216031604c118a0046e0d2a68a10400081801000305004039d323723c7d1600160144b3441602444e17030c0db3d47fff4ea11603411341907b16006c170416040c344c1e16041340fe2f4c07a0026e0d2a68a1024a0b1604130e2f4c07a0036e0d2a68a1034a0006a0046ea0026ea0036e60000818010301010107d3c9b5c135160000091341917b000802010000000008d3b573a9b4b3ce03411341937b140108a10101000202001bd34d603decb3ce03411600ce00451a1341937ba00114010141a0000c0d6216010c0d62411a1341907b1601a1000002000201406da0011700a001140101414c1bce0756324c14ce0a6d1343e8a0046e2cce096e2d324c021401044a01140103a101ce00ce010dab4a02a001024a4039a001140102414c03ce00b04a4032a001140104414c4029a0046e0e2d0d2a1701ce0856324c0ba0026ea0036e334c021401054a01140106a1014a12a0026e1601344c03140105a1014a0aa0036e1601344c03140106a1014a02140104a1014a051341a2047c7b181600a001424c0da001140105414c06140107a1010c0cce03010b07a0015ea0010200000101010cb41600a50d2f16000d0dce0301051a02",
+  "codeHash": "beaf53d3b5dd017082ec263ff8787fc564202ff0a853c5038fc090665853c761",
   "fieldsSig": {
     "names": [
       "authorAddress",
@@ -125,16 +125,13 @@
       "name": "commit",
       "paramNames": [
         "membershipTokenId",
-        "commitment",
-        "nonce"
+        "commitment"
       ],
       "paramTypes": [
-        "ByteVec",
         "ByteVec",
         "ByteVec"
       ],
       "paramIsMutable": [
-        false,
         false,
         false
       ],
diff --git a/artifacts/DAOkitVotingBallot.ral.json b/artifacts/DAOkitVotingBallot.ral.json
index 807ac32..9c691f3 100644
--- a/artifacts/DAOkitVotingBallot.ral.json
+++ b/artifacts/DAOkitVotingBallot.ral.json
@@ -1,22 +1,19 @@
 {
   "version": "v3.11.1",
   "name": "DAOkitVotingBallot",
-  "bytecode": "03030e4029405b010000000103d37fff4ea1a0010201000202000ad3ed37c7ebb3ce00411341937b1601a1001600a101010201020018d3328d5deeb44717011600a000441601444ea001411341937b1600a00016010f0cce00d423723c7d160163b0",
-  "codeHash": "fd07f2a8171749c8414517e0ae1ac964e3c413ad23832e26abfd0f331ba3c5b6",
+  "bytecode": "02030e4025406a010000000103d37fff4ea1a00002010001010008d3f66674adb3ce00411341937b1600a10001020203004023d3c5d0622cb4471702140100160144ce00441602444ea000411341937b1600160144ce00441602444e00011600160116020f0cce00d423723c7d160263b0",
+  "codeHash": "8d50fb7af4255e3169da9a813756b14e95fc299793a8aad35e8514c7ef00ac66",
   "fieldsSig": {
     "names": [
       "proposalId",
-      "nonce",
       "commitment"
     ],
     "types": [
-      "ByteVec",
       "ByteVec",
       "ByteVec"
     ],
     "isMutable": [
       false,
-      true,
       true
     ]
   },
@@ -34,15 +31,12 @@
     {
       "name": "setCommitment",
       "paramNames": [
-        "newCommitment",
-        "newNonce"
+        "newCommitment"
       ],
       "paramTypes": [
-        "ByteVec",
         "ByteVec"
       ],
       "paramIsMutable": [
-        false,
         false
       ],
       "returnTypes": []
@@ -50,12 +44,15 @@
     {
       "name": "validate",
       "paramNames": [
-        "vote"
+        "vote",
+        "nonce"
       ],
       "paramTypes": [
+        "ByteVec",
         "ByteVec"
       ],
       "paramIsMutable": [
+        false,
         false
       ],
       "returnTypes": []
diff --git a/artifacts/ts/DAOkitProposal.ts b/artifacts/ts/DAOkitProposal.ts
index 0314209..c7697c7 100644
--- a/artifacts/ts/DAOkitProposal.ts
+++ b/artifacts/ts/DAOkitProposal.ts
@@ -88,7 +88,6 @@ export namespace DAOkitProposalTypes {
       params: CallContractParams<{
         membershipTokenId: HexString;
         commitment: HexString;
-        nonce: HexString;
       }>;
       result: CallContractResult<null>;
     };
@@ -154,7 +153,6 @@ export namespace DAOkitProposalTypes {
       params: SignExecuteContractMethodParams<{
         membershipTokenId: HexString;
         commitment: HexString;
-        nonce: HexString;
       }>;
       result: SignExecuteScriptTxResult;
     };
@@ -274,11 +272,7 @@ class Factory extends ContractFactory<
     commit: async (
       params: TestContractParamsWithoutMaps<
         DAOkitProposalTypes.Fields,
-        {
-          membershipTokenId: HexString;
-          commitment: HexString;
-          nonce: HexString;
-        }
+        { membershipTokenId: HexString; commitment: HexString }
       >
     ): Promise<TestContractResultWithoutMaps<null>> => {
       return testMethod(this, "commit", params, getContractByCodeHash);
@@ -351,8 +345,8 @@ class Factory extends ContractFactory<
 export const DAOkitProposal = new Factory(
   Contract.fromJson(
     DAOkitProposalContractJson,
-    "",
-    "57d32f70aa33e4db44e99e03a7defe1d7078479cda98eb7fbbd92de9c9cb9539",
+    "=18-2+2b=2-2=2-2=2-2+59=1-2+18=3+56=2+6c=327-1+b=86-2+4020=35-1+d=32+0216027e024022426c616e6b20766f74652076616c6964617465642066726f6d206164647265737320=672",
+    "60d3e6ae2c83db135852d36f3b29fcb41df693d53fd46fd48931f1430793e1ef",
     []
   )
 );
diff --git a/artifacts/ts/DAOkitVotingBallot.ts b/artifacts/ts/DAOkitVotingBallot.ts
index 7437f51..6c93601 100644
--- a/artifacts/ts/DAOkitVotingBallot.ts
+++ b/artifacts/ts/DAOkitVotingBallot.ts
@@ -40,7 +40,6 @@ import { getContractByCodeHash, registerContract } from "./contracts";
 export namespace DAOkitVotingBallotTypes {
   export type Fields = {
     proposalId: HexString;
-    nonce: HexString;
     commitment: HexString;
   };
 
@@ -52,14 +51,11 @@ export namespace DAOkitVotingBallotTypes {
       result: CallContractResult<HexString>;
     };
     setCommitment: {
-      params: CallContractParams<{
-        newCommitment: HexString;
-        newNonce: HexString;
-      }>;
+      params: CallContractParams<{ newCommitment: HexString }>;
       result: CallContractResult<null>;
     };
     validate: {
-      params: CallContractParams<{ vote: HexString }>;
+      params: CallContractParams<{ vote: HexString; nonce: HexString }>;
       result: CallContractResult<null>;
     };
   }
@@ -85,14 +81,14 @@ export namespace DAOkitVotingBallotTypes {
       result: SignExecuteScriptTxResult;
     };
     setCommitment: {
-      params: SignExecuteContractMethodParams<{
-        newCommitment: HexString;
-        newNonce: HexString;
-      }>;
+      params: SignExecuteContractMethodParams<{ newCommitment: HexString }>;
       result: SignExecuteScriptTxResult;
     };
     validate: {
-      params: SignExecuteContractMethodParams<{ vote: HexString }>;
+      params: SignExecuteContractMethodParams<{
+        vote: HexString;
+        nonce: HexString;
+      }>;
       result: SignExecuteScriptTxResult;
     };
   }
@@ -130,7 +126,7 @@ class Factory extends ContractFactory<
     setCommitment: async (
       params: TestContractParamsWithoutMaps<
         DAOkitVotingBallotTypes.Fields,
-        { newCommitment: HexString; newNonce: HexString }
+        { newCommitment: HexString }
       >
     ): Promise<TestContractResultWithoutMaps<null>> => {
       return testMethod(this, "setCommitment", params, getContractByCodeHash);
@@ -138,7 +134,7 @@ class Factory extends ContractFactory<
     validate: async (
       params: TestContractParamsWithoutMaps<
         DAOkitVotingBallotTypes.Fields,
-        { vote: HexString }
+        { vote: HexString; nonce: HexString }
       >
     ): Promise<TestContractResultWithoutMaps<null>> => {
       return testMethod(this, "validate", params, getContractByCodeHash);
@@ -159,7 +155,7 @@ export const DAOkitVotingBallot = new Factory(
   Contract.fromJson(
     DAOkitVotingBallotContractJson,
     "",
-    "fd07f2a8171749c8414517e0ae1ac964e3c413ad23832e26abfd0f331ba3c5b6",
+    "8d50fb7af4255e3169da9a813756b14e95fc299793a8aad35e8514c7ef00ac66",
     []
   )
 );
diff --git a/contracts/ballot.ral b/contracts/ballot.ral
index 45a3210..ae02ce5 100644
--- a/contracts/ballot.ral
+++ b/contracts/ballot.ral
@@ -1,6 +1,5 @@
 Contract DAOkitVotingBallot(
 	proposalId: ByteVec,
-	mut nonce: ByteVec,
 	mut commitment: ByteVec // blake2b!(vote ++ nonce ++ voter_address)
 ) implements IDAOkitVotingBallot {
 
@@ -9,16 +8,16 @@ Contract DAOkitVotingBallot(
 	}
 
 	@using(updateFields = true)
-	pub fn setCommitment(newCommitment: ByteVec, newNonce: ByteVec) -> () {
+	pub fn setCommitment(newCommitment: ByteVec) -> () {
 		checkCaller!(callerContractId!() == proposalId, 403u)
-		nonce = newNonce
 		commitment = newCommitment
 	}
 
 	@using(assetsInContract = true)
-	pub fn validate(vote: ByteVec) -> () {
+	pub fn validate(vote: ByteVec, nonce: ByteVec) -> () {
 		let revealer = toByteVec!(callerAddress!())
-		checkCaller!(blake2b!(vote ++ nonce ++ revealer) == commitment, 403u)
+		checkCaller!(blake2b!(#00 ++ nonce ++ proposalId ++ revealer) == commitment, 403u)
+		setCommitment(blake2b!(vote ++ nonce ++ proposalId ++ revealer))
 		IDAOkitProposal(proposalId).revealVote(vote, nonce, revealer)
 		destroySelf!(byteVecToAddress!(revealer))
 	}
diff --git a/contracts/interfaces/ballot_interface.ral b/contracts/interfaces/ballot_interface.ral
index f0e70e6..9fa7052 100644
--- a/contracts/interfaces/ballot_interface.ral
+++ b/contracts/interfaces/ballot_interface.ral
@@ -5,8 +5,8 @@ Interface IDAOkitVotingBallot {
 
 	// Called only by the proposal contract, to update the commitment if desired
 	@using(updateFields = true)
-	pub fn setCommitment(newCommitment: ByteVec, newNonce: ByteVec) -> ()
+	pub fn setCommitment(newCommitment: ByteVec) -> ()
 
 	// Called only by the revealing address, destroys the subcontract once the vote has been revealed and casted.
-	pub fn validate(vote: ByteVec) -> ()
+	pub fn validate(vote: ByteVec, nonce: ByteVec) -> ()
 }
diff --git a/contracts/interfaces/proposal_interface.ral b/contracts/interfaces/proposal_interface.ral
index d659dcf..6240338 100644
--- a/contracts/interfaces/proposal_interface.ral
+++ b/contracts/interfaces/proposal_interface.ral
@@ -17,7 +17,7 @@ Interface IDAOkitProposal {
 	// Can be called several times to change vote, during the registration / discussion / commitment phase,
 	// before revealing it.
 	@using(preapprovedAssets = true, updateFields = true)
-	pub fn commit(membershipTokenId: ByteVec, commitment: ByteVec, nonce: ByteVec) -> ()
+	pub fn commit(membershipTokenId: ByteVec, commitment: ByteVec) -> ()
 
 	// Usable by the address mentioned in a commitment only, to reveal and cast a vote.
 	@using(updateFields = true)
diff --git a/contracts/proposal.ral b/contracts/proposal.ral
index 12c78de..e1091b9 100644
--- a/contracts/proposal.ral
+++ b/contracts/proposal.ral
@@ -112,17 +112,17 @@ Contract DAOkitProposal(
 	}
 
 	@using(preapprovedAssets = true, updateFields = true)
-	pub fn commit(membershipTokenId: ByteVec, commitment: ByteVec, nonce: ByteVec) -> () {
+	pub fn commit(membershipTokenId: ByteVec, commitment: ByteVec) -> () {
 		checkCaller!(isAllowed(membershipTokenId), ErrorCodes.NotMember)
 		assert!(membershipTokenId != authorENFTid, ErrorCodes.NotAllowed)
 		let ballotId = subContractId!(membershipTokenId)
-		if (contractExists!(ballotId)) {
-			IDAOkitVotingBallot(ballotId).setCommitment(commitment, nonce)
+		if (contractExists!(ballotId) && state != ProposalStates.Proposed) {
+			IDAOkitVotingBallot(ballotId).setCommitment(commitment)
 		}
 		else {
+			assert!(state == ProposalStates.Proposed, ErrorCodes.BadRequest)
 			let (encodedImmutableFields, encodedMutableFields) = DAOkitVotingBallot.encodeFields!(
 				selfContractId!(),
-				nonce,
 				commitment
 			)
 			let _ = copyCreateSubContract!{callerAddress!() -> ALPH: minimalContractDeposit!(), commitmentDepositTokenId: commitmentDepositAmount}(
@@ -138,18 +138,21 @@ Contract DAOkitProposal(
 
 	@using(updateFields = true)
 	pub fn revealVote(vote: ByteVec, nonce: ByteVec, revealer: ByteVec) -> () {
-		let verifiedCommitment = blake2b!(vote ++ nonce ++ revealer)
+		let verifiedCommitment = blake2b!(vote ++ nonce ++ callerContractId!() ++ revealer)
 		checkCaller!(IDAOkitVotingBallot(callerContractId!()).getCommitment() == verifiedCommitment, ErrorCodes.BadRequest)
 		let guessableVote = u256From1Byte!(vote)
 		if (guessableVote >= 0u) {
-			if (guessableVote % 2u == 0u) {
+			if (guessableVote == 0xFE) {
 				acceptedCount = u256To4Byte!(u256From4Byte!(acceptedCount) + 1u)
 			}
-			else {
+			else if (guessableVote == 0x0E) {
 				rejectedCount = u256To4Byte!(u256From4Byte!(rejectedCount) + 1u)
 			}
+			else {
+				emit Debug(`Blank vote validated from address ${revealer}`)
+			}
+			emit ProposalVote(u256From4Byte!(committedCount), u256From4Byte!(acceptedCount), u256From4Byte!(rejectedCount))
 		}
-		emit ProposalVote(u256From4Byte!(committedCount), u256From4Byte!(acceptedCount), u256From4Byte!(rejectedCount))
 		let _ = updateState()
 	}
 
-- 
GitLab


From d21eeb69dda2f60d8af7203cbc73da73c52f835e Mon Sep 17 00:00:00 2001
From: Fabio Bonfiglio <fabio.bonfiglio@protonmail.ch>
Date: Thu, 27 Feb 2025 20:03:26 +0100
Subject: [PATCH 38/40] Adapt to revised Proposed - Voting - Validating -
 Enacted scheme

---
 .project.json                               | 18 ++--
 artifacts/DAOkitOrg.ral.json                |  4 +-
 artifacts/DAOkitProposal.ral.json           | 53 ++++++++++--
 artifacts/DAOkitVotingBallot.ral.json       | 17 +++-
 artifacts/ts/DAOkitOrg.ts                   |  2 +-
 artifacts/ts/DAOkitProposal.ts              | 91 ++++++++++++++++++---
 artifacts/ts/DAOkitVotingBallot.ts          | 41 +++++++++-
 contracts/ballot.ral                        | 20 +++--
 contracts/interfaces/ballot_interface.ral   |  2 +
 contracts/interfaces/proposal_interface.ral |  2 +-
 contracts/organization.ral                  | 15 ++--
 contracts/proposal.ral                      | 42 +++++++---
 test/organization.test.ts                   |  6 ++
 test/proposal.test.ts                       | 60 ++++++++++----
 14 files changed, 299 insertions(+), 74 deletions(-)

diff --git a/.project.json b/.project.json
index f46fd56..290d063 100644
--- a/.project.json
+++ b/.project.json
@@ -13,21 +13,21 @@
   "infos": {
     "DAOkitOrg": {
       "sourceFile": "organization.ral",
-      "sourceCodeHash": "c65b9bf2b7189cee99ee0db7e210c3aa5fc650ea7fddd6f08702f44c26bd94f7",
+      "sourceCodeHash": "a20cbb884b0991d0a193dc49b171b61081df0150da6de8af716aa9447d9732b6",
       "bytecodeDebugPatch": "",
-      "codeHashDebug": "44443d439acd9891b3f8b98cc31173e3828fc89fd7a38c298c2798daa491866e"
+      "codeHashDebug": "794a247aedd0ab622dfe5c734469c044f0ffd6385a19fd0458edf910aa5bc1a1"
     },
     "DAOkitProposal": {
       "sourceFile": "proposal.ral",
-      "sourceCodeHash": "cde38a842acbc4c7d10014fc27ffdd0a4d66e7fd9b275d6f023e9f6017c9b7bf",
-      "bytecodeDebugPatch": "=18-2+2b=2-2=2-2=2-2+59=1-2+18=3+56=2+6c=327-1+b=86-2+4020=35-1+d=32+0216027e024022426c616e6b20766f74652076616c6964617465642066726f6d206164647265737320=672",
-      "codeHashDebug": "60d3e6ae2c83db135852d36f3b29fcb41df693d53fd46fd48931f1430793e1ef"
+      "sourceCodeHash": "35ccba71d32c1801af87d6ca58fba0222051b11572bf860bbb92c38d93b69d9f",
+      "bytecodeDebugPatch": "=18-3+5=1-2=2-2+67=2-2+7f=1+1b=1-2=2-2+92=3-1+842d5=401-1+c=88-2+4020=35-1+d=32+0216027e024022426c616e6b20766f74652076616c6964617465642066726f6d206164647265737320=806",
+      "codeHashDebug": "ff8e2eb16e72a7b12f62a5472e250dfc93ec14765f83bad7297b492a8bcb5b56"
     },
     "DAOkitVotingBallot": {
       "sourceFile": "ballot.ral",
-      "sourceCodeHash": "076dc5febcc5964a9dd83fdf149277ea89b560b283717c091502a67a899ee022",
+      "sourceCodeHash": "455172106396c9e176ef4397d510a5dcad1a7f9943cc08c0ceeccaceeb1cacc1",
       "bytecodeDebugPatch": "",
-      "codeHashDebug": "8d50fb7af4255e3169da9a813756b14e95fc299793a8aad35e8514c7ef00ac66"
+      "codeHashDebug": "f315a0fc515e5d3b918b830a18424398bccff309dbd9153d5b127ff9f8b13723"
     },
     "IDAOkitOrg": {
       "sourceFile": "interfaces/organization_interface.ral",
@@ -37,13 +37,13 @@
     },
     "IDAOkitProposal": {
       "sourceFile": "interfaces/proposal_interface.ral",
-      "sourceCodeHash": "bc48e3a394f80edfb60bfcb951b227f2f250e4bff61d2766ea1a912a65b06083",
+      "sourceCodeHash": "5b5e2b754647df5fd364f1ffba0a198842715bad6696a0c78cb94bf33f360dce",
       "bytecodeDebugPatch": "",
       "codeHashDebug": ""
     },
     "IDAOkitVotingBallot": {
       "sourceFile": "interfaces/ballot_interface.ral",
-      "sourceCodeHash": "0f4da80f119263253e49e6f28c6e462fc30866207fa65b95743cad32c46b3ec7",
+      "sourceCodeHash": "2ddc1e5f119d6231ef2096987686d3123065391ee7c6a2bce377d4605a97f170",
       "bytecodeDebugPatch": "",
       "codeHashDebug": ""
     },
diff --git a/artifacts/DAOkitOrg.ral.json b/artifacts/DAOkitOrg.ral.json
index 5f8061c..d0a065f 100644
--- a/artifacts/DAOkitOrg.ral.json
+++ b/artifacts/DAOkitOrg.ral.json
@@ -1,8 +1,8 @@
 {
   "version": "v3.11.1",
   "name": "DAOkitOrg",
-  "bytecode": "0b0c0e1c403940474062408a414c416d419241ee423b42e3010000000103d3facdcc89a00002010000000103d3926e1fc1a0010201000000070cd3fa5d1fcba001a0026fa0036fa0046fa005a006a00702010000000103d3d22165c1a0080201000102010bd3ecf235d916006bcb17011601c51341947b160102010001030112d30fec8d94160017010c0e160101011817021602a001410c0d1601d4fbe73eea1a020103040c01406ad33ad9a30a160000051341917ba0080d2a17041602a0036f344c0556a0036f2a4a0d1602a0026f324c0556a0026f2a4a035616022a17051603a0046f324c051605a0046f2a4a03160516032a170616010c0d626c17070d16070d2b0e2c2a1708b41600ce01b1a006a0051604160516061404ffffffffa007160816080e2a62130b641601140101140480000000140400000001140400000001130564170a1709b47ad1a216000da3160440ce001609160ac1170b1604a108160b0201000203000fd3ed1a13f816006bcb17021602c51341947bb416010e0c1602010700000101001614010416000c0d62411341907b16000f0f16000d0f626d2a62a10006a0005e0000010100402f14010516000c0d62411341907b16000d132162a10116001321132962a10216001329133162a10316001331133962a104160013391340596271a105160013405913407962a106160013407913408362a10706a0005e0002010300402514010316000c0d62411341907b16000d132262631701160013221340426217021602132065424c05160113206513c3038d7ea4c68000ab1601160216001340421340626271ab01000004004049d307d5527db317000c0e1600d46590af8f17011816010004b3411341937b0c0d1600d4d0148c55140107411341907b0c0d1600d41de3aebe170216020c0d6217031603140102414c06140454686973140c544f20494d504c454d454e54411341a27b4a1a1603140103414c031602000a4a131603140104414c03160200084a0c1603140105414c03160200094a051603140101411341907b0c0c1600d4b573a9b4",
-  "codeHash": "44443d439acd9891b3f8b98cc31173e3828fc89fd7a38c298c2798daa491866e",
+  "bytecode": "0b0c0e1c403940474062408a4169418a41af420b42584300010000000103d3facdcc89a00002010000000103d3926e1fc1a0010201000000070cd3fa5d1fcba001a0026fa0036fa0046fa005a006a00702010000000103d3d22165c1a0080201000102010bd3ecf235d916006bcb17011601c51341947b160102010001030112d30fec8d94160017010c0e160101011817021602a001410c0d1601d4fbe73eea1a020103040e014075d33ad9a30a160000051341917ba0080d2a17041602a0036f344c0556a0036f2a4a0d1602a0026f324c0556a0026f2a4a035616022a17051603a0046f324c03a0046f4a07160313c048190800334c0213c0481908004a0116031706160516062a1707160716062a170816010c0d626c17090d16090d2b0e2c2a170ab41600ce01b1a006a00516041605160716081404ffffffffa007160a160a0e2a62130c641601140101140480000000140400000001140400000001130564170c170bb47ad1a216000da3160440ce00160b160cc1170d1604a108160d0201000203000fd3ed1a13f816006bcb17021602c51341947bb416010e0c1602010700000101001614010416000c0d62411341907b16000f0f16000d0f626d2a62a10006a0005e0000010100402f14010516000c0d62411341907b16000d132162a10116001321132962a10216001329133162a10316001331133962a104160013391340596271a105160013405913407962a106160013407913408362a10706a0005e0002010300402514010316000c0d62411341907b16000d132262631701160013221340426217021602132065424c05160113206513c3038d7ea4c68000ab1601160216001340421340626271ab01000004004049d307d5527db317000c0e1600d46590af8f17011816010004b3411341937b0c0d1600d4d0148c55140108411341907b0c0d1600d41de3aebe170216020c0d6217031603140102414c06140454686973140c544f20494d504c454d454e54411341a27b4a1a1603140103414c031602000a4a131603140104414c03160200084a0c1603140105414c03160200094a051603140101411341907b0c0c1600d4b573a9b4",
+  "codeHash": "794a247aedd0ab622dfe5c734469c044f0ffd6385a19fd0458edf910aa5bc1a1",
   "fieldsSig": {
     "names": [
       "proposalTemplateId",
diff --git a/artifacts/DAOkitProposal.ral.json b/artifacts/DAOkitProposal.ral.json
index 6ba4cd0..8fc3a0c 100644
--- a/artifacts/DAOkitProposal.ral.json
+++ b/artifacts/DAOkitProposal.ral.json
@@ -1,8 +1,8 @@
 {
   "version": "v3.11.1",
   "name": "DAOkitProposal",
-  "bytecode": "100a101e402c409d41014117412f4162422c4242010000000204d36590af8fce03ce0602010000000103d3d0148c55a00102010000000103d31de3aebea000020103020500403cd3a5776feb160000091341917b1600ce01421341937b1600cb17021602c5a001140101421a4c0616010d0c1602d4f66674ad4a4020a001140101411341907bb1130164160113016417041703b47ad1a2ce04ce05a31600ce0216031604c118a0046e0d2a68a10400081801000305004039d323723c7d1600160144b3441602444e17030c0db3d47fff4ea11603411341907b16006c170416040c344c1e16041340fe2f4c07a0026e0d2a68a1024a0b1604130e2f4c07a0036e0d2a68a1034a0006a0046ea0026ea0036e60000818010301010107d3c9b5c135160000091341917b000802010000000008d3b573a9b4b3ce03411341937b140108a10101000202001bd34d603decb3ce03411600ce00451a1341937ba00114010141a0000c0d6216010c0d62411a1341907b1601a1000002000201406da0011700a001140101414c1bce0756324c14ce0a6d1343e8a0046e2cce096e2d324c021401044a01140103a101ce00ce010dab4a02a001024a4039a001140102414c03ce00b04a4032a001140104414c4029a0046e0e2d0d2a1701ce0856324c0ba0026ea0036e334c021401054a01140106a1014a12a0026e1601344c03140105a1014a0aa0036e1601344c03140106a1014a02140104a1014a051341a2047c7b181600a001424c0da001140105414c06140107a1010c0cce03010b07a0015ea0010200000101010cb41600a50d2f16000d0dce0301051a02",
-  "codeHash": "beaf53d3b5dd017082ec263ff8787fc564202ff0a853c5038fc090665853c761",
+  "bytecode": "110b101e402c40c24127413d415541884268427e42ab010000000204d36590af8fce03ce0602010000000103d3d0148c55a00102010000000103d31de3aebea000020103020600403fd3a5776feb160000091341917b1600ce01421341937bb44717021602cb17031603c5a001140101421a4c0616010d0c1603d4f66674ad4a4020a001140101411341907bb1130164144020000000000000000000000000000000000000000000000000000000000000000013016417051704b47ad1a2ce04ce05a31602ce0216041605c118a0046e0d2a68a1040008180100030500403ad3de6a01911600160144b344160247444e17030c0db3d47fff4ea11603411341907b16006c170416040c334c1e16041340fe2f4c07a0026e0d2a68a1024a0b1604130e2f4c07a0036e0d2a68a1034a0007a0046ea0026ea0036e60000818010301010107d3c9b5c135160000091341917b000802010000000008d3b573a9b4b3ce03411341937b140109a10101000202001bd34d603decb3ce03411600ce00451a1341937ba00114010141a0000c0d6216010c0d62411a1341907b1601a10000020002014078a0011700a001140101414c1bce0756324c14ce0b6d1343e8a0046e2cce0a6e2d324c021401044a01140103a101ce00ce010dab4a02a001024a4044a001140102414c03ce00b04a403da001140104414c07ce0856324c02140105a1014a4032a001140105414c4029a0046e0e2d0d2a1701ce0956324c0ba0026ea0036e334c021401064a01140107a1014a12a0026e1601344c03140106a1014a0aa0036e1601344c03140107a1014a02140105a1014a051341a2047c7b181600a001424c0da001140106414c06140108a1010c0cce03010b08a0015ea0010200000101010cb41600a50d2f16000d0dce0301051a02010001020014d3021aa367160047cb17011601c51341947ba0016c1306341341907b16000d0c1601d45e25c1d6",
+  "codeHash": "0cdfc56caa00bb185bd46868699983ab01984377f62864a03868901af3d42ced",
   "fieldsSig": {
     "names": [
       "authorAddress",
@@ -14,6 +14,7 @@
       "proposalIndex",
       "commitmentPeriodEnd",
       "votingPeriodEnd",
+      "validatingPeriodEnd",
       "maxVoters",
       "requiredQuorum",
       "payload",
@@ -32,6 +33,7 @@
       "U256",
       "U256",
       "U256",
+      "U256",
       "ByteVec",
       "ByteVec",
       "ByteVec",
@@ -52,6 +54,7 @@
       false,
       false,
       false,
+      false,
       true,
       true,
       true,
@@ -69,6 +72,15 @@
         "ByteVec"
       ]
     },
+    {
+      "name": "ProposalVoteRegistration",
+      "fieldNames": [
+        "address"
+      ],
+      "fieldTypes": [
+        "Address"
+      ]
+    },
     {
       "name": "ProposalVote",
       "fieldNames": [
@@ -147,7 +159,7 @@
       "paramTypes": [
         "ByteVec",
         "ByteVec",
-        "ByteVec"
+        "Address"
       ],
       "paramIsMutable": [
         false,
@@ -217,6 +229,19 @@
       "returnTypes": [
         "Bool"
       ]
+    },
+    {
+      "name": "retrieveUnvalidatedDeposit",
+      "paramNames": [
+        "voter"
+      ],
+      "paramTypes": [
+        "Address"
+      ],
+      "paramIsMutable": [
+        false
+      ],
+      "returnTypes": []
     }
   ],
   "constants": [],
@@ -244,6 +269,13 @@
             "type": "U256",
             "value": "403"
           }
+        },
+        {
+          "name": "NotFound",
+          "value": {
+            "type": "U256",
+            "value": "404"
+          }
         }
       ]
     },
@@ -279,32 +311,39 @@
           }
         },
         {
-          "name": "Accepted",
+          "name": "Validating",
           "value": {
             "type": "ByteVec",
             "value": "05"
           }
         },
         {
-          "name": "Rejected",
+          "name": "Accepted",
           "value": {
             "type": "ByteVec",
             "value": "06"
           }
         },
         {
-          "name": "Executing",
+          "name": "Rejected",
           "value": {
             "type": "ByteVec",
             "value": "07"
           }
         },
         {
-          "name": "Enacted",
+          "name": "Executing",
           "value": {
             "type": "ByteVec",
             "value": "08"
           }
+        },
+        {
+          "name": "Enacted",
+          "value": {
+            "type": "ByteVec",
+            "value": "09"
+          }
         }
       ]
     }
diff --git a/artifacts/DAOkitVotingBallot.ral.json b/artifacts/DAOkitVotingBallot.ral.json
index 9c691f3..0fb8a14 100644
--- a/artifacts/DAOkitVotingBallot.ral.json
+++ b/artifacts/DAOkitVotingBallot.ral.json
@@ -1,8 +1,8 @@
 {
   "version": "v3.11.1",
   "name": "DAOkitVotingBallot",
-  "bytecode": "02030e4025406a010000000103d37fff4ea1a00002010001010008d3f66674adb3ce00411341937b1600a10001020203004023d3c5d0622cb4471702140100160144ce00441602444ea000411341937b1600160144ce00441602444e00011600160116020f0cce00d423723c7d160263b0",
-  "codeHash": "8d50fb7af4255e3169da9a813756b14e95fc299793a8aad35e8514c7ef00ac66",
+  "bytecode": "02040e4025407a4090010000000103d37fff4ea1a00002010001010008d3f66674adb3ce00411341937b1600a1000102020500402ad3c5d0622cb41702ce0017030c0d1603d4d0148c55170416046c11341341907b1600160144ce0044160247444ea000411341937b1604140105414c071600160116020f0cce00d4de6a01911602b0010201010008d35e25c1d6b3ce00411341937b1600b0",
+  "codeHash": "f315a0fc515e5d3b918b830a18424398bccff309dbd9153d5b127ff9f8b13723",
   "fieldsSig": {
     "names": [
       "proposalId",
@@ -56,6 +56,19 @@
         false
       ],
       "returnTypes": []
+    },
+    {
+      "name": "retrieveDeposit",
+      "paramNames": [
+        "recipient"
+      ],
+      "paramTypes": [
+        "Address"
+      ],
+      "paramIsMutable": [
+        false
+      ],
+      "returnTypes": []
     }
   ],
   "constants": [],
diff --git a/artifacts/ts/DAOkitOrg.ts b/artifacts/ts/DAOkitOrg.ts
index edeb3fa..1ef4d71 100644
--- a/artifacts/ts/DAOkitOrg.ts
+++ b/artifacts/ts/DAOkitOrg.ts
@@ -378,7 +378,7 @@ export const DAOkitOrg = new Factory(
   Contract.fromJson(
     DAOkitOrgContractJson,
     "",
-    "44443d439acd9891b3f8b98cc31173e3828fc89fd7a38c298c2798daa491866e",
+    "794a247aedd0ab622dfe5c734469c044f0ffd6385a19fd0458edf910aa5bc1a1",
     []
   )
 );
diff --git a/artifacts/ts/DAOkitProposal.ts b/artifacts/ts/DAOkitProposal.ts
index c7697c7..c89677f 100644
--- a/artifacts/ts/DAOkitProposal.ts
+++ b/artifacts/ts/DAOkitProposal.ts
@@ -48,6 +48,7 @@ export namespace DAOkitProposalTypes {
     proposalIndex: bigint;
     commitmentPeriodEnd: bigint;
     votingPeriodEnd: bigint;
+    validatingPeriodEnd: bigint;
     maxVoters: HexString;
     requiredQuorum: HexString;
     payload: HexString;
@@ -62,6 +63,9 @@ export namespace DAOkitProposalTypes {
   export type ProposalUpdatedEvent = ContractEvent<{
     updatedPayload: HexString;
   }>;
+  export type ProposalVoteRegistrationEvent = ContractEvent<{
+    address: Address;
+  }>;
   export type ProposalVoteEvent = ContractEvent<{
     participants: bigint;
     accepted: bigint;
@@ -95,7 +99,7 @@ export namespace DAOkitProposalTypes {
       params: CallContractParams<{
         vote: HexString;
         nonce: HexString;
-        revealer: HexString;
+        revealer: Address;
       }>;
       result: CallContractResult<null>;
     };
@@ -119,6 +123,10 @@ export namespace DAOkitProposalTypes {
       params: CallContractParams<{ membershipTokenId: HexString }>;
       result: CallContractResult<boolean>;
     };
+    retrieveUnvalidatedDeposit: {
+      params: CallContractParams<{ voter: Address }>;
+      result: CallContractResult<null>;
+    };
   }
   export type CallMethodParams<T extends keyof CallMethodTable> =
     CallMethodTable[T]["params"];
@@ -160,7 +168,7 @@ export namespace DAOkitProposalTypes {
       params: SignExecuteContractMethodParams<{
         vote: HexString;
         nonce: HexString;
-        revealer: HexString;
+        revealer: Address;
       }>;
       result: SignExecuteScriptTxResult;
     };
@@ -187,6 +195,10 @@ export namespace DAOkitProposalTypes {
       params: SignExecuteContractMethodParams<{ membershipTokenId: HexString }>;
       result: SignExecuteScriptTxResult;
     };
+    retrieveUnvalidatedDeposit: {
+      params: SignExecuteContractMethodParams<{ voter: Address }>;
+      result: SignExecuteScriptTxResult;
+    };
   }
   export type SignExecuteMethodParams<T extends keyof SignExecuteMethodTable> =
     SignExecuteMethodTable[T]["params"];
@@ -206,22 +218,29 @@ class Factory extends ContractFactory<
     );
   }
 
-  eventIndex = { ProposalUpdated: 0, ProposalVote: 1, ProposalStateChanged: 2 };
+  eventIndex = {
+    ProposalUpdated: 0,
+    ProposalVoteRegistration: 1,
+    ProposalVote: 2,
+    ProposalStateChanged: 3,
+  };
   consts = {
     ErrorCodes: {
       BadRequest: BigInt("400"),
       NotMember: BigInt("401"),
       NotAllowed: BigInt("403"),
+      NotFound: BigInt("404"),
     },
     ProposalStates: {
       Proposed: "01",
       Cancelled: "02",
       Dropped: "03",
       Voting: "04",
-      Accepted: "05",
-      Rejected: "06",
-      Executing: "07",
-      Enacted: "08",
+      Validating: "05",
+      Accepted: "06",
+      Rejected: "07",
+      Executing: "08",
+      Enacted: "09",
     },
   };
 
@@ -280,7 +299,7 @@ class Factory extends ContractFactory<
     revealVote: async (
       params: TestContractParamsWithoutMaps<
         DAOkitProposalTypes.Fields,
-        { vote: HexString; nonce: HexString; revealer: HexString }
+        { vote: HexString; nonce: HexString; revealer: Address }
       >
     ): Promise<TestContractResultWithoutMaps<null>> => {
       return testMethod(this, "revealVote", params, getContractByCodeHash);
@@ -330,6 +349,19 @@ class Factory extends ContractFactory<
     ): Promise<TestContractResultWithoutMaps<boolean>> => {
       return testMethod(this, "isAllowed", params, getContractByCodeHash);
     },
+    retrieveUnvalidatedDeposit: async (
+      params: TestContractParamsWithoutMaps<
+        DAOkitProposalTypes.Fields,
+        { voter: Address }
+      >
+    ): Promise<TestContractResultWithoutMaps<null>> => {
+      return testMethod(
+        this,
+        "retrieveUnvalidatedDeposit",
+        params,
+        getContractByCodeHash
+      );
+    },
   };
 
   stateForTest(
@@ -345,8 +377,8 @@ class Factory extends ContractFactory<
 export const DAOkitProposal = new Factory(
   Contract.fromJson(
     DAOkitProposalContractJson,
-    "=18-2+2b=2-2=2-2=2-2+59=1-2+18=3+56=2+6c=327-1+b=86-2+4020=35-1+d=32+0216027e024022426c616e6b20766f74652076616c6964617465642066726f6d206164647265737320=672",
-    "60d3e6ae2c83db135852d36f3b29fcb41df693d53fd46fd48931f1430793e1ef",
+    "=18-3+5=1-2=2-2+67=2-2+7f=1+1b=1-2=2-2+92=3-1+842d5=401-1+c=88-2+4020=35-1+d=32+0216027e024022426c616e6b20766f74652076616c6964617465642066726f6d206164647265737320=806",
+    "ff8e2eb16e72a7b12f62a5472e250dfc93ec14765f83bad7297b492a8bcb5b56",
     []
   )
 );
@@ -379,6 +411,19 @@ export class DAOkitProposalInstance extends ContractInstance {
     );
   }
 
+  subscribeProposalVoteRegistrationEvent(
+    options: EventSubscribeOptions<DAOkitProposalTypes.ProposalVoteRegistrationEvent>,
+    fromCount?: number
+  ): EventSubscription {
+    return subscribeContractEvent(
+      DAOkitProposal.contract,
+      this,
+      options,
+      "ProposalVoteRegistration",
+      fromCount
+    );
+  }
+
   subscribeProposalVoteEvent(
     options: EventSubscribeOptions<DAOkitProposalTypes.ProposalVoteEvent>,
     fromCount?: number
@@ -408,6 +453,7 @@ export class DAOkitProposalInstance extends ContractInstance {
   subscribeAllEvents(
     options: EventSubscribeOptions<
       | DAOkitProposalTypes.ProposalUpdatedEvent
+      | DAOkitProposalTypes.ProposalVoteRegistrationEvent
       | DAOkitProposalTypes.ProposalVoteEvent
       | DAOkitProposalTypes.ProposalStateChangedEvent
     >,
@@ -534,6 +580,19 @@ export class DAOkitProposalInstance extends ContractInstance {
         getContractByCodeHash
       );
     },
+    retrieveUnvalidatedDeposit: async (
+      params: DAOkitProposalTypes.CallMethodParams<"retrieveUnvalidatedDeposit">
+    ): Promise<
+      DAOkitProposalTypes.CallMethodResult<"retrieveUnvalidatedDeposit">
+    > => {
+      return callMethod(
+        DAOkitProposal,
+        this,
+        "retrieveUnvalidatedDeposit",
+        params,
+        getContractByCodeHash
+      );
+    },
   };
 
   transact = {
@@ -615,6 +674,18 @@ export class DAOkitProposalInstance extends ContractInstance {
     ): Promise<DAOkitProposalTypes.SignExecuteMethodResult<"isAllowed">> => {
       return signExecuteMethod(DAOkitProposal, this, "isAllowed", params);
     },
+    retrieveUnvalidatedDeposit: async (
+      params: DAOkitProposalTypes.SignExecuteMethodParams<"retrieveUnvalidatedDeposit">
+    ): Promise<
+      DAOkitProposalTypes.SignExecuteMethodResult<"retrieveUnvalidatedDeposit">
+    > => {
+      return signExecuteMethod(
+        DAOkitProposal,
+        this,
+        "retrieveUnvalidatedDeposit",
+        params
+      );
+    },
   };
 
   async multicall<Calls extends DAOkitProposalTypes.MultiCallParams>(
diff --git a/artifacts/ts/DAOkitVotingBallot.ts b/artifacts/ts/DAOkitVotingBallot.ts
index 6c93601..8852265 100644
--- a/artifacts/ts/DAOkitVotingBallot.ts
+++ b/artifacts/ts/DAOkitVotingBallot.ts
@@ -58,6 +58,10 @@ export namespace DAOkitVotingBallotTypes {
       params: CallContractParams<{ vote: HexString; nonce: HexString }>;
       result: CallContractResult<null>;
     };
+    retrieveDeposit: {
+      params: CallContractParams<{ recipient: Address }>;
+      result: CallContractResult<null>;
+    };
   }
   export type CallMethodParams<T extends keyof CallMethodTable> =
     CallMethodTable[T]["params"];
@@ -91,6 +95,10 @@ export namespace DAOkitVotingBallotTypes {
       }>;
       result: SignExecuteScriptTxResult;
     };
+    retrieveDeposit: {
+      params: SignExecuteContractMethodParams<{ recipient: Address }>;
+      result: SignExecuteScriptTxResult;
+    };
   }
   export type SignExecuteMethodParams<T extends keyof SignExecuteMethodTable> =
     SignExecuteMethodTable[T]["params"];
@@ -139,6 +147,14 @@ class Factory extends ContractFactory<
     ): Promise<TestContractResultWithoutMaps<null>> => {
       return testMethod(this, "validate", params, getContractByCodeHash);
     },
+    retrieveDeposit: async (
+      params: TestContractParamsWithoutMaps<
+        DAOkitVotingBallotTypes.Fields,
+        { recipient: Address }
+      >
+    ): Promise<TestContractResultWithoutMaps<null>> => {
+      return testMethod(this, "retrieveDeposit", params, getContractByCodeHash);
+    },
   };
 
   stateForTest(
@@ -155,7 +171,7 @@ export const DAOkitVotingBallot = new Factory(
   Contract.fromJson(
     DAOkitVotingBallotContractJson,
     "",
-    "8d50fb7af4255e3169da9a813756b14e95fc299793a8aad35e8514c7ef00ac66",
+    "f315a0fc515e5d3b918b830a18424398bccff309dbd9153d5b127ff9f8b13723",
     []
   )
 );
@@ -205,6 +221,17 @@ export class DAOkitVotingBallotInstance extends ContractInstance {
         getContractByCodeHash
       );
     },
+    retrieveDeposit: async (
+      params: DAOkitVotingBallotTypes.CallMethodParams<"retrieveDeposit">
+    ): Promise<DAOkitVotingBallotTypes.CallMethodResult<"retrieveDeposit">> => {
+      return callMethod(
+        DAOkitVotingBallot,
+        this,
+        "retrieveDeposit",
+        params,
+        getContractByCodeHash
+      );
+    },
   };
 
   transact = {
@@ -237,6 +264,18 @@ export class DAOkitVotingBallotInstance extends ContractInstance {
     ): Promise<DAOkitVotingBallotTypes.SignExecuteMethodResult<"validate">> => {
       return signExecuteMethod(DAOkitVotingBallot, this, "validate", params);
     },
+    retrieveDeposit: async (
+      params: DAOkitVotingBallotTypes.SignExecuteMethodParams<"retrieveDeposit">
+    ): Promise<
+      DAOkitVotingBallotTypes.SignExecuteMethodResult<"retrieveDeposit">
+    > => {
+      return signExecuteMethod(
+        DAOkitVotingBallot,
+        this,
+        "retrieveDeposit",
+        params
+      );
+    },
   };
 
   async multicall<Calls extends DAOkitVotingBallotTypes.MultiCallParams>(
diff --git a/contracts/ballot.ral b/contracts/ballot.ral
index ae02ce5..e4cda7e 100644
--- a/contracts/ballot.ral
+++ b/contracts/ballot.ral
@@ -15,10 +15,20 @@ Contract DAOkitVotingBallot(
 
 	@using(assetsInContract = true)
 	pub fn validate(vote: ByteVec, nonce: ByteVec) -> () {
-		let revealer = toByteVec!(callerAddress!())
-		checkCaller!(blake2b!(#00 ++ nonce ++ proposalId ++ revealer) == commitment, 403u)
-		setCommitment(blake2b!(vote ++ nonce ++ proposalId ++ revealer))
-		IDAOkitProposal(proposalId).revealVote(vote, nonce, revealer)
-		destroySelf!(byteVecToAddress!(revealer))
+		let revealer = callerAddress!()
+		let proposal = IDAOkitProposal(proposalId)
+		let proposalState = proposal.getProposalState()
+		assert!(u256From1Byte!(proposalState) >= 0x05, 400u)
+		checkCaller!(blake2b!(vote ++ nonce ++ proposalId ++ toByteVec!(revealer)) == commitment, 403u)
+		if (proposalState == #05) {
+			IDAOkitProposal(proposalId).revealVote(vote, nonce, revealer)
+		}
+		destroySelf!(revealer)
+	}
+
+	@using(assetsInContract = true)
+	pub fn retrieveDeposit(recipient: Address) -> () {
+		checkCaller!(callerContractId!() == proposalId, 403u)
+		destroySelf!(recipient)
 	}
 }
diff --git a/contracts/interfaces/ballot_interface.ral b/contracts/interfaces/ballot_interface.ral
index 9fa7052..37399e9 100644
--- a/contracts/interfaces/ballot_interface.ral
+++ b/contracts/interfaces/ballot_interface.ral
@@ -9,4 +9,6 @@ Interface IDAOkitVotingBallot {
 
 	// Called only by the revealing address, destroys the subcontract once the vote has been revealed and casted.
 	pub fn validate(vote: ByteVec, nonce: ByteVec) -> ()
+
+	pub fn retrieveDeposit(recipient: Address) -> ()
 }
diff --git a/contracts/interfaces/proposal_interface.ral b/contracts/interfaces/proposal_interface.ral
index 6240338..4119e82 100644
--- a/contracts/interfaces/proposal_interface.ral
+++ b/contracts/interfaces/proposal_interface.ral
@@ -21,7 +21,7 @@ Interface IDAOkitProposal {
 
 	// Usable by the address mentioned in a commitment only, to reveal and cast a vote.
 	@using(updateFields = true)
-	pub fn revealVote(vote: ByteVec, nonce: ByteVec, revealer: ByteVec) -> ()
+	pub fn revealVote(vote: ByteVec, nonce: ByteVec, revealer: Address) -> ()
 
 	// Checks if the state has to be updated, and returns the state after eventual update.
 	// Also enacts proposal's defined actions if proposal reaches accepted state.
diff --git a/contracts/organization.ral b/contracts/organization.ral
index c95af07..4ec8cdc 100644
--- a/contracts/organization.ral
+++ b/contracts/organization.ral
@@ -5,7 +5,7 @@ Contract DAOkitOrg(
 	mut membershipCollectionId: ByteVec,
 	mut minimalCommitmentPeriod: ByteVec, // Expressed in milliseconds and stored in a 8-bytes integer.
 	mut maximalCommitmentPeriod: ByteVec, // Same format as minimalCommitmentPeriod.
-	mut minimalVotingPeriod: ByteVec, // Same format as minimalCommitmentPeriod. No maximum needed as vote is concluded as soon as a majority is reached.
+	mut minimalVotingPeriod: ByteVec, // Same format as minimalCommitmentPeriod.
 	mut commitmentDepositAmount: U256,
 	mut commitmentDepositTokenId: ByteVec,
 	mut requiredQuorums: ByteVec, // Expressed in ‰ (per mille) and stored in 2-bytes integers per proposal types, with index being the proposal type value - 1. See https://gitlab.fbo.network/alephium/daokit/-/wikis/Concept#proposal-variants
@@ -75,10 +75,14 @@ Contract DAOkitOrg(
 			blockTimeStamp!() + u256From8Byte!(minimalCommitmentPeriod)
 		else
 			blockTimeStamp!() + commitmentPeriod
-		let votingEnd = if (votingPeriod <= u256From8Byte!(minimalVotingPeriod))
-			commitmentEnd + u256From8Byte!(minimalVotingPeriod)
+		let votingDuration = if (votingPeriod <= u256From8Byte!(minimalVotingPeriod))
+			u256From8Byte!(minimalVotingPeriod)
+		else if (votingPeriod > 1209600000u)
+			1209600000u
 		else
-			commitmentEnd + votingPeriod
+			votingPeriod
+		let votingEnd = commitmentEnd + votingDuration
+		let validatingEnd = votingEnd + votingDuration
 		let proposalType = u256From1Byte!(byteVecSlice!(payload, 0u, 1u))
 		let quorumValueOffset = 1u + (proposalType - 1u) * 2u
 		let (encodedImmutableFields, encodedMutableFields) = DAOkitProposal.encodeFields!(
@@ -91,6 +95,7 @@ Contract DAOkitOrg(
 			proposalIndex,
 			commitmentEnd,
 			votingEnd,
+			validatingEnd,
 			#FFFFFFFF, // TODO: retrieve supply from ENFT collection / certificator contract
 			byteVecSlice!(requiredQuorums, quorumValueOffset, quorumValueOffset + 2u),
 			payload,
@@ -159,7 +164,7 @@ Contract DAOkitOrg(
 		let proposal = IDAOkitProposal(callerContractId!())
 		let (_, proposalIndex) = proposal.getOrganizationIndex()
 		checkCaller!(contractId!(proposalByIndex(proposalIndex)) == callerContractId!(), ErrorCodes.NotAllowed) // TODO: adapt this same pattern to ENFT certificator discovery
-		assert!(proposal.getProposalState() == #07, ErrorCodes.BadRequest)
+		assert!(proposal.getProposalState() == #08, ErrorCodes.BadRequest)
 		let payload = proposal.getProposalPayload()
 		let proposalType = byteVecSlice!(payload, 0u, 1u)
 		// TODO: Add a review process for motions as well ?
diff --git a/contracts/proposal.ral b/contracts/proposal.ral
index e1091b9..8f677e8 100644
--- a/contracts/proposal.ral
+++ b/contracts/proposal.ral
@@ -8,6 +8,7 @@ Contract DAOkitProposal(
 	proposalIndex: U256,
 	commitmentPeriodEnd: U256,
 	votingPeriodEnd: U256,
+	validatingPeriodEnd: U256,
 	maxVoters: ByteVec, // 4 Bytes
 	requiredQuorum: ByteVec, // Expressed in ‰ (per mille) and stored in 2-bytes integer
 	mut payload: ByteVec,
@@ -18,6 +19,7 @@ Contract DAOkitProposal(
 ) implements IDAOkitProposal {
 
 	event ProposalUpdated(updatedPayload: ByteVec)
+	event ProposalVoteRegistration(address: Address) // This event can be used in the case of implementing a off-chain tallier that would verify all commitment proofs that would be submitted by voters during validation instead of revealing their vote. It also can be used by orgs to redeem all unvalidated deposits.
 	event ProposalVote(participants: U256, accepted: U256, rejected: U256)
 	event ProposalStateChanged(updatedState: ByteVec)
 
@@ -25,7 +27,7 @@ Contract DAOkitProposal(
 		BadRequest = 400
 		NotMember = 401
 		NotAllowed = 403
-		//NotFound = 404
+		NotFound = 404
 	}
 
 	enum ProposalStates {
@@ -33,10 +35,11 @@ Contract DAOkitProposal(
 		Cancelled = #02
 		Dropped = #03
 		Voting = #04
-		Accepted = #05
-		Rejected = #06
-		Executing = #07
-		Enacted = #08
+		Validating = #05
+		Accepted = #06
+		Rejected = #07
+		Executing = #08
+		Enacted = #09
 	}
 
 	pub fn getOrganizationIndex() -> (ByteVec, U256) {
@@ -77,8 +80,13 @@ Contract DAOkitProposal(
 			destroySelf!(authorAddress)
 		}
 		else if (state == ProposalStates.Voting) {
-			let absoluteMajority = u256From4Byte!(committedCount) / 2u + 1u
 			if (votingPeriodEnd <= blockTimeStamp!()) {
+				state = ProposalStates.Validating
+			}
+		}
+		else if (state == ProposalStates.Validating) {
+			let absoluteMajority = u256From4Byte!(committedCount) / 2u + 1u
+			if (validatingPeriodEnd <= blockTimeStamp!()) {
 				state = if (u256From4Byte!(acceptedCount) > u256From4Byte!(rejectedCount))
 					ProposalStates.Accepted
 				else
@@ -91,7 +99,7 @@ Contract DAOkitProposal(
 				state = ProposalStates.Rejected
 			}
 			else {
-				state = ProposalStates.Voting
+				state = ProposalStates.Validating
 			}
 		}
 		else {
@@ -115,7 +123,8 @@ Contract DAOkitProposal(
 	pub fn commit(membershipTokenId: ByteVec, commitment: ByteVec) -> () {
 		checkCaller!(isAllowed(membershipTokenId), ErrorCodes.NotMember)
 		assert!(membershipTokenId != authorENFTid, ErrorCodes.NotAllowed)
-		let ballotId = subContractId!(membershipTokenId)
+		let ballotPath = toByteVec!(callerAddress!())
+		let ballotId = subContractId!(ballotPath)
 		if (contractExists!(ballotId) && state != ProposalStates.Proposed) {
 			IDAOkitVotingBallot(ballotId).setCommitment(commitment)
 		}
@@ -123,10 +132,10 @@ Contract DAOkitProposal(
 			assert!(state == ProposalStates.Proposed, ErrorCodes.BadRequest)
 			let (encodedImmutableFields, encodedMutableFields) = DAOkitVotingBallot.encodeFields!(
 				selfContractId!(),
-				commitment
+				#0000000000000000000000000000000000000000000000000000000000000000
 			)
 			let _ = copyCreateSubContract!{callerAddress!() -> ALPH: minimalContractDeposit!(), commitmentDepositTokenId: commitmentDepositAmount}(
-				membershipTokenId,
+				ballotPath,
 				ballotTemplateId,
 				encodedImmutableFields,
 				encodedMutableFields
@@ -137,11 +146,11 @@ Contract DAOkitProposal(
 	}
 
 	@using(updateFields = true)
-	pub fn revealVote(vote: ByteVec, nonce: ByteVec, revealer: ByteVec) -> () {
-		let verifiedCommitment = blake2b!(vote ++ nonce ++ callerContractId!() ++ revealer)
+	pub fn revealVote(vote: ByteVec, nonce: ByteVec, revealer: Address) -> () {
+		let verifiedCommitment = blake2b!(vote ++ nonce ++ callerContractId!() ++ toByteVec!(revealer))
 		checkCaller!(IDAOkitVotingBallot(callerContractId!()).getCommitment() == verifiedCommitment, ErrorCodes.BadRequest)
 		let guessableVote = u256From1Byte!(vote)
-		if (guessableVote >= 0u) {
+		if (guessableVote > 0u) {
 			if (guessableVote == 0xFE) {
 				acceptedCount = u256To4Byte!(u256From4Byte!(acceptedCount) + 1u)
 			}
@@ -156,6 +165,13 @@ Contract DAOkitProposal(
 		let _ = updateState()
 	}
 
+	pub fn retrieveUnvalidatedDeposit(voter: Address) -> () {
+		let ballotId = subContractId!(toByteVec!(voter))
+		checkCaller!(contractExists!(ballotId), ErrorCodes.NotFound)
+		assert!(u256From1Byte!(state) >= 0x06, ErrorCodes.BadRequest)
+		IDAOkitVotingBallot(ballotId).retrieveDeposit(voter)
+	}
+
 	@using(updateFields = true)
 	pub fn confirmExecution() -> () {
 		checkCaller!(callerContractId!() == organizationId, ErrorCodes.NotAllowed)
diff --git a/test/organization.test.ts b/test/organization.test.ts
index 3771e5c..35c0d6e 100644
--- a/test/organization.test.ts
+++ b/test/organization.test.ts
@@ -124,6 +124,12 @@ describe("DAOkitOrg unit tests", () => {
 		expect(testResult.returns).toEqual(42n);
 	});
 
+	it.todo("Author should be able to change a proposal payload if types are matching");
+
+	it.todo("Author should not be able to set a new payload if proposal types aren't matching");
+
+	it.todo("Shouldn't be able to change a proposal payload if not the author");
+
 	it("Private method should modify org's URI when passed correct payload", async () => {
 		const NEW_URI = "https://example.com/newOrgFrontend";
 		const testResult = await DAOkitOrg.tests.setOrganizationUri({
diff --git a/test/proposal.test.ts b/test/proposal.test.ts
index d780e7c..1bd59f7 100644
--- a/test/proposal.test.ts
+++ b/test/proposal.test.ts
@@ -19,6 +19,7 @@ describe("DAOkitProposal unit tests", () => {
 	const FOUR_HOURS = 4000n * 3600n;
 	const testCommitPeriodEnd = BigInt(Date.now()) + ONE_DAY;
 	const testVotingPeriodEnd = testCommitPeriodEnd + ONE_DAY;
+	const testValidatingPeriodEnd = testVotingPeriodEnd + ONE_DAY;
 	const testMaxVoters = Math.round(Math.random() * 4096) + 420;
 	const testRequiredQuorums = 20.2;
 	const GENERIC_MOTION_PAYLOAD = uintToByteVec(1, 1).concat(stringToHex(TEST_METADATA_URI));
@@ -47,6 +48,7 @@ describe("DAOkitProposal unit tests", () => {
 				proposalIndex: testProposalIndex,
 				commitmentPeriodEnd: testCommitPeriodEnd,
 				votingPeriodEnd: testVotingPeriodEnd,
+				validatingPeriodEnd: testValidatingPeriodEnd,
 				maxVoters,
 				requiredQuorum,
 				payload: GENERIC_MOTION_PAYLOAD,
@@ -82,6 +84,7 @@ describe("DAOkitProposal unit tests", () => {
 				proposalIndex: testProposalIndex,
 				commitmentPeriodEnd: testCommitPeriodEnd,
 				votingPeriodEnd: testVotingPeriodEnd,
+				validatingPeriodEnd: testValidatingPeriodEnd,
 				maxVoters,
 				requiredQuorum,
 				payload: GENERIC_MOTION_PAYLOAD,
@@ -114,6 +117,7 @@ describe("DAOkitProposal unit tests", () => {
 				proposalIndex: testProposalIndex,
 				commitmentPeriodEnd: testCommitPeriodEnd,
 				votingPeriodEnd: testVotingPeriodEnd,
+				validatingPeriodEnd: testValidatingPeriodEnd,
 				maxVoters,
 				requiredQuorum,
 				payload: GENERIC_MOTION_PAYLOAD,
@@ -133,6 +137,7 @@ describe("DAOkitProposal unit tests", () => {
 		const state = "01"; // Proposed
 		const commitmentPeriodEnd = BigInt(Date.now()) - FOUR_HOURS;
 		const votingPeriodEnd = commitmentPeriodEnd + ONE_DAY;
+		const validatingPeriodEnd = votingPeriodEnd + ONE_DAY;
 		const EXPECTED_STATE = "03"; // Dropped
 		const testResult = await DAOkitProposal.tests.updateState({
 			address: addressFromContractId(testProposalId),
@@ -155,6 +160,7 @@ describe("DAOkitProposal unit tests", () => {
 				proposalIndex: testProposalIndex,
 				commitmentPeriodEnd,
 				votingPeriodEnd,
+				validatingPeriodEnd,
 				maxVoters,
 				requiredQuorum,
 				payload: GENERIC_MOTION_PAYLOAD,
@@ -181,6 +187,7 @@ describe("DAOkitProposal unit tests", () => {
 		const state = "01"; // Proposed
 		const commitmentPeriodEnd = BigInt(Date.now()) - FOUR_HOURS;
 		const votingPeriodEnd = commitmentPeriodEnd + ONE_DAY;
+		const validatingPeriodEnd = votingPeriodEnd + ONE_DAY;
 		const EXPECTED_STATE = "04"; // Voting
 		const testResult = await DAOkitProposal.tests.updateState({
 			address: addressFromContractId(testProposalId),
@@ -203,6 +210,7 @@ describe("DAOkitProposal unit tests", () => {
 				proposalIndex: testProposalIndex,
 				commitmentPeriodEnd,
 				votingPeriodEnd,
+				validatingPeriodEnd,
 				maxVoters,
 				requiredQuorum,
 				payload: GENERIC_MOTION_PAYLOAD,
@@ -220,6 +228,10 @@ describe("DAOkitProposal unit tests", () => {
 		expect(testResult.events[0].fields.updatedState).toEqual(EXPECTED_STATE);
 	});
 
+	it.todo("Should change state from Voting to Validating when voting period ends");
+
+	it.todo("Should not change state when Voting period not ended yet");
+
 	test.each([
 		[11, 0, 0],
 		[11, 4, 4],
@@ -243,16 +255,17 @@ describe("DAOkitProposal unit tests", () => {
 		[8399, 0, 2400],
 		[8399, 4199, 4199],
 		[8399, 4199, 4200]
-	])("Should reject proposal if majority not reached (%p participants, %p accepted and %p rejected) when voting period ends", async (participants: number, accepts: number, rejects: number) => {
+	])("Should reject proposal if majority not reached (%p participants, %p accepted and %p rejected) when validating period ends", async (participants: number, accepts: number, rejects: number) => {
 		const requiredQuorum = uintToByteVec(200n, 2);
 		const maxVoters = uintToByteVec(Math.floor(participants / 0.2) - participants, 4);
 		const committedCount = uintToByteVec(participants, 4);
-		const state = "04"; // Voting
+		const state = "05"; // Validating
 		const acceptedCount = uintToByteVec(accepts, 4);
 		const rejectedCount = uintToByteVec(rejects, 4);
 		const commitmentPeriodEnd = BigInt(Date.now()) - FOUR_HOURS;
 		const votingPeriodEnd = commitmentPeriodEnd + 3600000n; // Voting ended 3 hours ago
-		const EXPECTED_STATE = "06"; // Rejected
+		const validatingPeriodEnd = votingPeriodEnd + 3600000n; // Validating ended 2 hours ago
+		const EXPECTED_STATE = "07"; // Rejected
 		const testResult = await DAOkitProposal.tests.updateState({
 			address: addressFromContractId(testProposalId),
 			initialAsset: {
@@ -274,6 +287,7 @@ describe("DAOkitProposal unit tests", () => {
 				proposalIndex: testProposalIndex,
 				commitmentPeriodEnd,
 				votingPeriodEnd,
+				validatingPeriodEnd,
 				maxVoters,
 				requiredQuorum,
 				payload: GENERIC_MOTION_PAYLOAD,
@@ -308,16 +322,17 @@ describe("DAOkitProposal unit tests", () => {
 		[8399, 843, 842],
 		[8399, 2400, 0],
 		[8399, 4200, 4199]
-	])("Should execute proposal if majority is reached (%p participants, %p accepted and %p rejected) when voting period ends", async (participants: number, accepts: number, rejects: number) => {
+	])("Should execute proposal if majority is reached (%p participants, %p accepted and %p rejected) when validating period ends", async (participants: number, accepts: number, rejects: number) => {
 		const requiredQuorum = uintToByteVec(200n, 2);
 		const maxVoters = uintToByteVec(Math.floor(participants / 0.2) - participants, 4);
 		const committedCount = uintToByteVec(participants, 4);
-		const state = "04"; // Voting
+		const state = "05"; // Validating
 		const acceptedCount = uintToByteVec(accepts, 4);
 		const rejectedCount = uintToByteVec(rejects, 4);
 		const commitmentPeriodEnd = BigInt(Date.now()) - FOUR_HOURS;
 		const votingPeriodEnd = commitmentPeriodEnd + 3600000n; // Voting ended 3 hours ago
-		const EXPECTED_STATE = "08"; // Enacted
+		const validatingPeriodEnd = votingPeriodEnd + 3600000n; // Validating ended 2 hours ago
+		const EXPECTED_STATE = "09"; // Enacted
 		const testResult = await DAOkitProposal.tests.updateState({
 			address: addressFromContractId(testProposalId),
 			group: 0,
@@ -340,6 +355,7 @@ describe("DAOkitProposal unit tests", () => {
 				proposalIndex: testProposalIndex,
 				commitmentPeriodEnd,
 				votingPeriodEnd,
+				validatingPeriodEnd,
 				maxVoters,
 				requiredQuorum,
 				payload: GENERIC_MOTION_PAYLOAD,
@@ -406,16 +422,17 @@ describe("DAOkitProposal unit tests", () => {
 		[8399, 4198, 4200],
 		[8399, 4199, 4200],
 		[8400, 4199, 4201]
-	])("Should immediately reject proposal if absolute majority is reached (%p participants, %p accepted and %p rejected) before voting period ends", async (participants: number, accepts: number, rejects: number) => {
+	])("Should immediately reject proposal if absolute majority is reached (%p participants, %p accepted and %p rejected) before validating period ends", async (participants: number, accepts: number, rejects: number) => {
 		const requiredQuorum = uintToByteVec(200n, 2);
 		const maxVoters = uintToByteVec(Math.floor(participants / 0.2) - participants, 4);
 		const committedCount = uintToByteVec(participants, 4);
-		const state = "04"; // Voting
+		const state = "05"; // Validating
 		const acceptedCount = uintToByteVec(accepts, 4);
 		const rejectedCount = uintToByteVec(rejects, 4);
 		const commitmentPeriodEnd = BigInt(Date.now()) - FOUR_HOURS;
-		const votingPeriodEnd = commitmentPeriodEnd + ONE_DAY; // Voting period ends in 20 hours
-		const EXPECTED_STATE = "06"; // Rejected
+		const votingPeriodEnd = commitmentPeriodEnd + 3600000n ; // Voting period ended 3 hours ago
+		const validatingPeriodEnd = votingPeriodEnd + ONE_DAY; // Validating ends in 23 hours
+		const EXPECTED_STATE = "07"; // Rejected
 		const testResult = await DAOkitProposal.tests.updateState({
 			address: addressFromContractId(testProposalId),
 			initialAsset: {
@@ -437,6 +454,7 @@ describe("DAOkitProposal unit tests", () => {
 				proposalIndex: testProposalIndex,
 				commitmentPeriodEnd,
 				votingPeriodEnd,
+				validatingPeriodEnd,
 				maxVoters,
 				requiredQuorum,
 				payload: GENERIC_MOTION_PAYLOAD,
@@ -475,16 +493,17 @@ describe("DAOkitProposal unit tests", () => {
 		[8399, 4200, 1],
 		[8399, 4200, 4199],
 		[8400, 4201, 4199]
-	])("Should immediately execute proposal if absolute majority is reached (%p participants, %p accepted and %p rejected) before voting period ends", async (participants: number, accepts: number, rejects: number) => {
+	])("Should immediately execute proposal if absolute majority is reached (%p participants, %p accepted and %p rejected) before validating period ends", async (participants: number, accepts: number, rejects: number) => {
 		const requiredQuorum = uintToByteVec(200n, 2);
 		const maxVoters = uintToByteVec(Math.floor(participants / 0.2) - participants, 4);
 		const committedCount = uintToByteVec(participants, 4);
-		const state = "04"; // Voting
+		const state = "05"; // Validating
 		const acceptedCount = uintToByteVec(accepts, 4);
 		const rejectedCount = uintToByteVec(rejects, 4);
 		const commitmentPeriodEnd = BigInt(Date.now()) - FOUR_HOURS;
-		const votingPeriodEnd = commitmentPeriodEnd + ONE_DAY; // Voting period ends in 20 hours
-		const EXPECTED_STATE = "08"; // Enacted
+		const votingPeriodEnd = commitmentPeriodEnd + 3600000n; // Voting period ended 3 hours ago
+		const validatingPeriodEnd = votingPeriodEnd + ONE_DAY; // Validating ends in 23 hours
+		const EXPECTED_STATE = "09"; // Enacted
 		const testResult = await DAOkitProposal.tests.updateState({
 			address: addressFromContractId(testProposalId),
 			group: 0,
@@ -507,6 +526,7 @@ describe("DAOkitProposal unit tests", () => {
 				proposalIndex: testProposalIndex,
 				commitmentPeriodEnd,
 				votingPeriodEnd,
+				validatingPeriodEnd,
 				maxVoters,
 				requiredQuorum,
 				payload: GENERIC_MOTION_PAYLOAD,
@@ -556,6 +576,7 @@ describe("DAOkitProposal unit tests", () => {
 		const state = "01"; // Proposed
 		const commitmentPeriodEnd = BigInt(Date.now()) + FOUR_HOURS; // Commitment period ends in four hours
 		const votingPeriodEnd = commitmentPeriodEnd + ONE_DAY;
+		const validatingPeriodEnd = votingPeriodEnd + ONE_DAY;
 		const EXPECTED_STATE = state; // Still Proposed !
 		const testResult = await DAOkitProposal.tests.updateState({
 			address: addressFromContractId(testProposalId),
@@ -578,6 +599,7 @@ describe("DAOkitProposal unit tests", () => {
 				proposalIndex: testProposalIndex,
 				commitmentPeriodEnd,
 				votingPeriodEnd,
+				validatingPeriodEnd,
 				maxVoters,
 				requiredQuorum,
 				payload: GENERIC_MOTION_PAYLOAD,
@@ -618,16 +640,17 @@ describe("DAOkitProposal unit tests", () => {
 		[8399, 4199, 4199],
 		[8400, 4199, 4200],
 		[8400, 4200, 4199]
-	])("Shouldn't change state when in Voting and conditions (%p participants, %p accepts and %p rejects) for closing vote aren't met and period not ended", async (participants: number, accepts: number, rejects: number) => {
+	])("Shouldn't change state when in Validating and conditions (%p participants, %p accepts and %p rejects) for closing validation aren't met", async (participants: number, accepts: number, rejects: number) => {
 		const requiredQuorum = uintToByteVec(200n, 2);
 		const maxVoters = uintToByteVec(Math.floor(participants / 0.2) - participants, 4);
 		const committedCount = uintToByteVec(participants, 4);
-		const state = "04"; // Voting
+		const state = "05"; // Validating
 		const acceptedCount = uintToByteVec(accepts, 4);
 		const rejectedCount = uintToByteVec(rejects, 4);
 		const commitmentPeriodEnd = BigInt(Date.now()) - FOUR_HOURS;
-		const votingPeriodEnd = commitmentPeriodEnd + ONE_DAY; // Voting period ends in 20 hours
-		const EXPECTED_STATE = state; // Still Voting !
+		const votingPeriodEnd = commitmentPeriodEnd + 3600000n; // Voting period ended 3 hours ago
+		const validatingPeriodEnd = votingPeriodEnd + ONE_DAY; // Validating ends in 23 hours
+		const EXPECTED_STATE = state; // Still Validating !
 		const testResult = await DAOkitProposal.tests.updateState({
 			address: addressFromContractId(testProposalId),
 			group: 0,
@@ -650,6 +673,7 @@ describe("DAOkitProposal unit tests", () => {
 				proposalIndex: testProposalIndex,
 				commitmentPeriodEnd,
 				votingPeriodEnd,
+				validatingPeriodEnd,
 				maxVoters,
 				requiredQuorum,
 				payload: GENERIC_MOTION_PAYLOAD,
-- 
GitLab


From 638571dd3a5b4c02d7eb075e2c61b75f336224b2 Mon Sep 17 00:00:00 2001
From: Fabio Bonfiglio <fabio.bonfiglio@protonmail.ch>
Date: Thu, 27 Feb 2025 20:55:15 +0100
Subject: [PATCH 39/40] Add ballot unit tests

---
 test/ballot.test.ts | 41 +++++++++++++++++++++++++++++++++++++----
 1 file changed, 37 insertions(+), 4 deletions(-)

diff --git a/test/ballot.test.ts b/test/ballot.test.ts
index 973936c..34d6def 100644
--- a/test/ballot.test.ts
+++ b/test/ballot.test.ts
@@ -1,5 +1,38 @@
-import { MINIMAL_CONTRACT_DEPOSIT } from "@alephium/web3";
-import { randomContractId } from "@alephium/web3-test";
-import { DAOkitProposal, DAOkitVotingBallot } from "../artifacts/ts";
+import { addressFromContractId, binToHex, bs58, hexToString, MINIMAL_CONTRACT_DEPOSIT, stringToHex, subContractId, web3 } from "@alephium/web3";
+import { randomContractId, testAddress } from "@alephium/web3-test";
+import { DAOkitVotingBallot } from "../artifacts/ts";
+import { uintToByteVec } from "../src/lib/ralph-helpers";
 
-// TODO: implement unit tests for ballot contract
\ No newline at end of file
+jest.setTimeout(process.env.CI ? 2000 : 400);
+
+describe("DAOkitVotingBallot unit tests", () => {
+	const testProposalId = randomContractId(0);
+	const testBallotId = subContractId(testProposalId, binToHex(bs58.decode(testAddress)), 0);
+	const testRegistrationCommit = uintToByteVec(0, 32);
+	const rndvals = new Uint8Array(32);
+	const testCommitment = Array.from(crypto.getRandomValues(rndvals)).map(b => b.toString(16).padStart(2, "0")).join("");
+	const TEST_DEPOSIT_AMOUNT = 100n**19n;
+
+	beforeAll(() => {
+		web3.setCurrentNodeProvider(`http://${process.env.CI ? "devnet" : "localhost"}:22973`, undefined, fetch);
+	});
+
+	test.each([
+		[testRegistrationCommit],
+		[testCommitment]
+	])("Should return correct commitment (%p)", async (commitmentStr) => {
+		const testResult = await DAOkitVotingBallot.tests.getCommitment({
+			address: addressFromContractId(testBallotId),
+			initialAsset: {
+				alphAmount: MINIMAL_CONTRACT_DEPOSIT + TEST_DEPOSIT_AMOUNT,
+			},
+			initialFields: {
+				proposalId: testProposalId,
+				commitment: stringToHex(commitmentStr)
+			}
+		});
+		expect(hexToString(testResult.returns)).toEqual(commitmentStr);
+	});
+
+	it.todo("Test other methods in integration tests.");
+});
\ No newline at end of file
-- 
GitLab


From fc8376d49fa4fe6f343b5ad7f39feb3e71d2e75a Mon Sep 17 00:00:00 2001
From: Fabio Bonfiglio <fabio.bonfiglio@protonmail.ch>
Date: Thu, 27 Feb 2025 21:21:36 +0100
Subject: [PATCH 40/40] Adapt README

---
 README.md | 14 ++++++--------
 1 file changed, 6 insertions(+), 8 deletions(-)

diff --git a/README.md b/README.md
index 759be90..d262a8e 100644
--- a/README.md
+++ b/README.md
@@ -8,11 +8,11 @@ To be notified about latest updates and discuss this project, please join the Te
 
 ## Planned features
 ### MVP
-- [ ] **Orgs, Proposals and voting lifecycle** contracts
-- [ ] **Vote on governance rules changes**, and frontend changes as well
+- [x] **Orgs, Proposals and voting lifecycle** contracts
+- [x] **Vote on governance rules changes**, and frontend changes as well
 - [ ] **Fungible / NFT / [ENFT](https://gitlab.fbo.network/alephium/enft-certificates)-based membership**: see [below](#token-based-access).
 - [ ] **Modular proposals system**: Expandable payloads types, and enacting can spawn other proposal automatically (for instance, a grant, once accepted, would trigger another proposal to approve the transfer to the grantee, once the agreed outcome is attained).
-- [ ] **Treasury**: Manage organization's assets, and vote for transfers.
+- [x] **Treasury / payments**: Vote for organization's assets transfers.
 - [ ] **Customizable UI**: Built as a static website deployable on IPFS for a complete decentralization.
 - **A command line tool**, allowing
   - [ ] Configuration, preparation and deployment (using desktop wallet integration) of the DAO contracts
@@ -25,6 +25,7 @@ Extended goals may include:
 - [ ] **Agent**: to interact with other contracts, dapps, or even other DAOs.
 - [ ] **Payroll**: manage payrolls
 - [ ] **Code upgrade**: a proposal that would include bytecode in its payload, allowing to upgrade the Organization's contract (and thus also change the proposal and ballot templates).
+- [ ] **Complete vote confidentiality**: This is an important ultimate goal to be reached for DAOs in general. For now, votes are kept secret during the voting period, to avoid any kind of social influence or attack, thanks to a commit-reveal scheme, but are then only obfuscated after being validated, meaning on-chain analysis could potentially reveal what each member voted once it is enacted. But the currently implemented architecture allows to evolve the vote counting process to a third-party off-chain tallier that would verify ZK-proofs (probably using homomorphic encryption to hide the real vote payload even from the tallier itself) transmitted by each validating address, instead of having to reveal at validation.
 
 ## Token-based access
 An essential element of a DAO for it to work as expected is the reliability of its system for controlling access to its operational methods.  
@@ -42,10 +43,6 @@ Using the CLI tool, one or more tokens can be configured in the following ways:
 ## DAOkit standard interfaces proposal
 The [`contracts/interfaces`](contracts/interfaces/) directory contains interfaces that are to be proposed as a standard, so third parties could eventually implement optimized frontends for exploring DAOs and associated informations, metadata, ongoing proposals, and maybe even standard actions like proposals submitting, approvals, voting actions, etc.
 
-## Votes anonymity
-As an extended feature of the present project, votes anonymity is an important ultimate goal to be reached. Researching and developing solutions that allow for a zkProof-based, [NYM credentials](https://nymtech.net/about/zk-nyms), or [single use ring signatures](https://arxiv.org/pdf/1804.06674), etc, voting results is important for long-term adoption of DAOs in our societies.
-For now, votes can be kept anonymous during the voting period, thanks to a commit-reveal scheme, but are then only obfuscated after being revealed, meaning on-chain analysis could potentially reveal what a member voted.
-
 # Usage
 ## Prerequisites
 - A compatible UNIX shell environment, like [Bash](https://en.wikipedia.org/wiki/Bash_(Unix_shell)) or [Zsh](https://en.wikipedia.org/wiki/Z_shell), that are natively available on most linux distributions and on MacOS. For Microsoft customers, it may be necessary to install a [WSL2 environment](https://learn.microsoft.com/en-us/windows/wsl/install).
@@ -90,7 +87,8 @@ npm run dev
 - [x] Design UI features modules / components
 - [ ] Document DevOps / configuration and deployments procedures
 - [ ] Implement modules
-- [ ] Implement CLI tools
+- [ ] Implement UI components
+- [ ] Implement CLI tools (contracts generator, frontend generator, deployment automation and helpers)
 - [ ] ...
 
 ## :construction:
-- 
GitLab