From b94cc432dcc226f9f0d30a5b54baa6e541044c14 Mon Sep 17 00:00:00 2001 From: Pamphile Roy <23188539+tupui@users.noreply.github.com> Date: Tue, 3 Dec 2024 00:53:30 +0100 Subject: [PATCH] Backend for DAO (#93) --- Cargo.lock | 127 ++++----- Makefile | 5 +- contracts/versioning/src/contract_dao.rs | 192 ++++++++++++++ .../versioning/src/contract_versioning.rs | 200 +++++++++++++++ contracts/versioning/src/errors.rs | 21 ++ contracts/versioning/src/lib.rs | 241 +++--------------- contracts/versioning/src/test.rs | 72 +++++- contracts/versioning/src/types.rs | 67 +++++ .../soroban_versioning/dist/index.d.ts | 221 ++++++++++++++-- .../packages/soroban_versioning/dist/index.js | 28 +- dapp/packages/soroban_versioning/src/index.ts | 213 ++++++++++++++-- 11 files changed, 1061 insertions(+), 326 deletions(-) create mode 100644 contracts/versioning/src/contract_dao.rs create mode 100644 contracts/versioning/src/contract_versioning.rs create mode 100644 contracts/versioning/src/errors.rs create mode 100644 contracts/versioning/src/types.rs diff --git a/Cargo.lock b/Cargo.lock index 45b049aa..1182ea66 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] @@ -43,9 +43,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" @@ -133,9 +133,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.21" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07b1695e2c7e8fc85310cde85aeaab7e3097f593c91d209d3f9df76c928100f0" +checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc" dependencies = [ "shlex", ] @@ -173,9 +173,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.14" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ "libc", ] @@ -215,9 +215,9 @@ dependencies = [ [[package]] name = "ctor" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f" +checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" dependencies = [ "quote", "syn", @@ -462,9 +462,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.31.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "group" @@ -485,9 +485,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.14.5" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "hex" @@ -555,12 +555,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.5.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", - "hashbrown 0.14.5", + "hashbrown 0.15.2", "serde", ] @@ -581,16 +581,17 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "js-sys" -version = "0.3.70" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +checksum = "a865e038f7f6ed956f788f0d7d60c541fff74c7bd74272c5d4cf15c63743e705" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -617,15 +618,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.159" +version = "0.2.167" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" +checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" [[package]] name = "libm" -version = "0.2.8" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "log" @@ -695,18 +696,18 @@ dependencies = [ [[package]] name = "object" -version = "0.36.4" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "p256" @@ -753,9 +754,9 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.22" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" +checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" dependencies = [ "proc-macro2", "syn", @@ -772,9 +773,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -870,18 +871,18 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.210" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", @@ -890,9 +891,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.128" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ "itoa", "memchr", @@ -902,15 +903,15 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.9.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857" +checksum = "8e28bdad6db2b8340e449f7108f020b3b092e8583a9e3fb82713e1d4e71fe817" dependencies = [ "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.5.0", + "indexmap 2.7.0", "serde", "serde_derive", "serde_json", @@ -920,9 +921,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.9.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350" +checksum = "9d846214a9854ef724f3da161b426242d8de7c1fc7de2f89bb1efcb154dca79d" dependencies = [ "darling", "proc-macro2", @@ -1222,9 +1223,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.77" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", @@ -1233,18 +1234,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.64" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.64" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", @@ -1290,9 +1291,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "version_check" @@ -1316,9 +1317,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.93" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "d15e63b4482863c109d70a7b8706c1e364eb6ea449b201a76c5b89cedcec2d5c" dependencies = [ "cfg-if", "once_cell", @@ -1327,9 +1328,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.93" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +checksum = "8d36ef12e3aaca16ddd3f67922bc63e48e953f126de60bd33ccc0101ef9998cd" dependencies = [ "bumpalo", "log", @@ -1342,9 +1343,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.93" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +checksum = "705440e08b42d3e4b36de7d66c944be628d579796b8090bfa3471478a2260051" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1352,9 +1353,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.93" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +checksum = "98c9ae5a76e46f4deecd0f0255cc223cfa18dc9b261213b8aa0c7b36f61b3f1d" dependencies = [ "proc-macro2", "quote", @@ -1365,9 +1366,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.93" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49" [[package]] name = "wasmi_arena" @@ -1393,7 +1394,7 @@ version = "0.116.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a58e28b80dd8340cb07b8242ae654756161f6fc8d0038123d679b7b99964fa50" dependencies = [ - "indexmap 2.5.0", + "indexmap 2.7.0", "semver", ] diff --git a/Makefile b/Makefile index b3956c1e..d6bd161a 100644 --- a/Makefile +++ b/Makefile @@ -75,13 +75,14 @@ contract_build-release: contract_build @ls -l target/wasm32-unknown-unknown/release/*.wasm -contract_bindings: contract_build-release +contract_bindings: contract_build-release ## Create bindings stellar contract bindings typescript \ --network $(network) \ --contract-id $(shell cat .soroban/soroban_versioning_id) \ + --wasm target/wasm32-unknown-unknown/release/versioning.wasm \ --output-dir dapp/packages/soroban_versioning \ --overwrite && \ - cd packages/soroban_versioning && \ + cd dapp/packages/soroban_versioning && \ bun run build contract_deploy: ## Deploy Soroban contract to testnet diff --git a/contracts/versioning/src/contract_dao.rs b/contracts/versioning/src/contract_dao.rs new file mode 100644 index 00000000..36af5b95 --- /dev/null +++ b/contracts/versioning/src/contract_dao.rs @@ -0,0 +1,192 @@ +use crate::{errors, types, DaoTrait, Tansu, TansuClient}; +use soroban_sdk::{contractimpl, panic_with_error, vec, Address, Bytes, Env, String, Vec}; + +const MAX_TITLE_LENGTH: u32 = 256; +const MAX_PROPOSALS_PER_PAGE: u32 = 9; +const MAX_PAGES: u32 = 1000; +const MIN_VOTING_PERIOD: u64 = 24 * 3600; // 1 day in seconds +const MAX_VOTING_PERIOD: u64 = 30 * 24 * 3600; // 30 days in seconds + +#[contractimpl] +impl DaoTrait for Tansu { + /// Create a proposal on the DAO of the project. + /// Proposal initiators are automatically put in the abstain group. + /// # Arguments + /// * `env` - The environment object + /// * `proposer` - Address of the proposal creator + /// * `project_key` - Unique identifier for the project + /// * `title` - Title of the proposal + /// * `ipfs` - IPFS content identifier describing the proposal + /// * `voting_ends_at` - UNIX timestamp when voting ends + /// # Returns + /// * `u32` - The ID of the created proposal + fn create_proposal( + env: Env, + proposer: Address, + project_key: Bytes, + title: String, + ipfs: String, + voting_ends_at: u64, + ) -> u32 { + // Some input validations + let curr_timestamp = env.ledger().timestamp(); + let min_voting_timestamp = curr_timestamp + MIN_VOTING_PERIOD; + let max_voting_timestamp = curr_timestamp + MAX_VOTING_PERIOD; + let ipfs_len = ipfs.len(); + let title_len = title.len(); + + if !((min_voting_timestamp..=max_voting_timestamp).contains(&voting_ends_at) + && (10..=MAX_TITLE_LENGTH).contains(&title_len) + && (32..=64).contains(&ipfs_len)) + { + panic_with_error!(&env, &errors::ContractErrors::ProposalInputValidation); + } + + // proposers can only be maintainers of existing projects + let project_key_ = project_key.clone(); + let key_ = types::ProjectKey::Key(project_key_.clone()); + if let Some(project) = env + .storage() + .persistent() + .get::(&key_) + { + crate::auth_maintainers(&env, &proposer, &project.maintainers); + + let proposal_id = env + .storage() + .persistent() + .get(&types::ProjectKey::DaoTotalProposals(project_key_.clone())) + .unwrap_or(0); + + // proposer is automatically in the abstain group + let voters_abstain = vec![&env, proposer]; + let proposal = types::Proposal { + id: proposal_id, + title, + ipfs, + voting_ends_at, + voters_approve: Vec::new(&env), + voters_reject: Vec::new(&env), + voters_abstain, + nqg: 0, + status: types::ProposalStatus::Active, + }; + + let next_id = proposal_id + 1; + env.storage().persistent().set( + &types::ProjectKey::DaoTotalProposals(project_key_.clone()), + &next_id, + ); + + let page = proposal_id / MAX_PROPOSALS_PER_PAGE; + let mut dao_page = + ::get_dao(env.clone(), project_key_.clone(), page); + dao_page.proposals.push_back(proposal); + + env.storage().persistent().set( + &types::ProjectKey::Dao(project_key_.clone(), page), + &dao_page, + ); + + // probably publish an event + // env.events() + // .publish((symbol_short!("proposal"), project_key_.clone()), title); + + proposal_id + } else { + panic_with_error!(&env, &errors::ContractErrors::InvalidKey); + } + } + + /// Cast a vote on a proposal. + /// Double votes are not allowed. + /// # Arguments + /// * `env` - The environment object + /// * `voter` - Address of the voter + /// * `project_key` - Unique identifier for the project + /// * `proposal_id` - ID of the proposal + /// * `vote` - Approve, reject or abstain decision + fn vote(env: Env, voter: Address, project_key: Bytes, proposal_id: u32, vote: types::Vote) { + voter.require_auth(); + + let project_key_ = project_key.clone(); + let page = proposal_id / MAX_PROPOSALS_PER_PAGE; + let sub_id = proposal_id % MAX_PROPOSALS_PER_PAGE; + let mut dao_page = ::get_dao(env.clone(), project_key_.clone(), page); + let mut proposal = match dao_page.proposals.try_get(sub_id) { + Ok(Some(proposal)) => proposal, + _ => panic_with_error!(&env, &errors::ContractErrors::NoProposalorPageFound), + }; + + // only allow to vote once per voter + if proposal.voters_approve.contains(&voter) + | proposal.voters_reject.contains(&voter) + | proposal.voters_abstain.contains(&voter) + { + panic_with_error!(&env, &errors::ContractErrors::AlreadyVoted); + } else { + // Record the vote + match vote { + types::Vote::Approve => proposal.voters_approve.push_back(voter), + types::Vote::Reject => proposal.voters_reject.push_back(voter), + types::Vote::Abstain => proposal.voters_abstain.push_back(voter), + } + + dao_page.proposals.set(sub_id, proposal); + + env.storage() + .persistent() + .set(&types::ProjectKey::Dao(project_key_, page), &dao_page) + } + } + + /// Get one page of proposal of the DAO. + /// A page has 0 to MAX_PROPOSALS_PER_PAGE proposals. + /// # Arguments + /// * `env` - The environment object + /// * `project_key` - Unique identifier for the project + /// * `page` - Page of proposals + /// # Returns + /// * `types::Dao` - The Dao object (vector of proposals) + fn get_dao(env: Env, project_key: Bytes, page: u32) -> types::Dao { + if page >= MAX_PAGES { + panic_with_error!(&env, &errors::ContractErrors::NoProposalorPageFound); + } + + let project_key_ = project_key.clone(); + let key_ = types::ProjectKey::Key(project_key_.clone()); + if env + .storage() + .persistent() + .get::(&key_) + .is_some() + { + env.storage() + .persistent() + .get(&types::ProjectKey::Dao(project_key_, page)) + .unwrap_or(types::Dao { + proposals: Vec::new(&env), + }) + } else { + panic_with_error!(&env, &errors::ContractErrors::InvalidKey); + } + } + + /// Only return a single proposal + /// # Arguments + /// * `env` - The environment object + /// * `project_key` - Unique identifier for the project + /// * `proposal_id` - ID of the proposal + /// # Returns + /// * `types::Proposal` - The proposal object + fn get_proposal(env: Env, project_key: Bytes, proposal_id: u32) -> types::Proposal { + let page = proposal_id / MAX_PROPOSALS_PER_PAGE; + let sub_id = proposal_id % MAX_PROPOSALS_PER_PAGE; + let dao_page = ::get_dao(env.clone(), project_key, page); + let proposals = dao_page.proposals; + match proposals.try_get(sub_id) { + Ok(Some(proposal)) => proposal, + _ => panic_with_error!(&env, &errors::ContractErrors::NoProposalorPageFound), + } + } +} diff --git a/contracts/versioning/src/contract_versioning.rs b/contracts/versioning/src/contract_versioning.rs new file mode 100644 index 00000000..6d6c4d05 --- /dev/null +++ b/contracts/versioning/src/contract_versioning.rs @@ -0,0 +1,200 @@ +use soroban_sdk::{ + contractimpl, panic_with_error, symbol_short, vec, Address, Bytes, BytesN, Env, IntoVal, + String, Symbol, Val, Vec, +}; + +use crate::{domain_contract, errors, types, Tansu, TansuClient, VersioningTrait}; + +#[contractimpl] +impl VersioningTrait for Tansu { + fn init(env: Env, admin: Address) { + env.storage().instance().set(&types::DataKey::Admin, &admin); + } + + fn upgrade(env: Env, new_wasm_hash: BytesN<32>) { + let admin: Address = env + .storage() + .instance() + .get(&types::DataKey::Admin) + .unwrap(); + admin.require_auth(); + + env.deployer().update_current_contract_wasm(new_wasm_hash); + } + + fn version() -> u32 { + 1 + } + + /// Register a new Git projects and associated metadata. + fn register( + env: Env, + maintainer: Address, + name: String, + maintainers: Vec
, + url: String, + hash: String, + domain_contract_id: Address, + ) -> Bytes { + let project = types::Project { + name: name.clone(), + config: types::Config { url, hash }, + maintainers, + }; + let str_len = name.len() as usize; + if str_len > 15 { + // could add more checks but handled in any case with later calls + panic_with_error!(&env, &errors::ContractErrors::InvalidDomainError); + } + let mut slice: [u8; 15] = [0; 15]; + name.copy_into_slice(&mut slice[..str_len]); + let name_b = Bytes::from_slice(&env, &slice[0..str_len]); + + let key: Bytes = env.crypto().keccak256(&name_b).into(); + + let key_ = types::ProjectKey::Key(key.clone()); + if env + .storage() + .persistent() + .get::(&key_) + .is_some() + { + panic_with_error!(&env, &errors::ContractErrors::ProjectAlreadyExist); + } else { + crate::auth_maintainers(&env, &maintainer, &project.maintainers); + + let node = domain_node(&env, &key); + let record_keys = domain_contract::RecordKeys::Record(node); + + let domain_client = domain_contract::Client::new(&env, &domain_contract_id); + match domain_client.try_record(&record_keys) { + Ok(Ok(None)) => domain_register(&env, &name_b, &maintainer, domain_contract_id), + Ok(Ok(Some(domain_contract::Record::Domain(domain)))) => { + if domain.owner != maintainer { + panic_with_error!(&env, &errors::ContractErrors::MaintainerNotDomainOwner) + } + } + _ => panic_with_error!(&env, &errors::ContractErrors::InvalidDomainError), + } + env.storage().persistent().set(&key_, &project); + + env.events() + .publish((symbol_short!("register"), key.clone()), name); + key + } + } + + /// Change the configuration of the project. + fn update_config( + env: Env, + maintainer: Address, + key: Bytes, + maintainers: Vec
, + url: String, + hash: String, + ) { + let key_ = types::ProjectKey::Key(key); + if let Some(mut project) = env + .storage() + .persistent() + .get::(&key_) + { + crate::auth_maintainers(&env, &maintainer, &project.maintainers); + + let config = types::Config { url, hash }; + project.config = config; + project.maintainers = maintainers; + env.storage().persistent().set(&key_, &project); + } else { + panic_with_error!(&env, &errors::ContractErrors::InvalidKey); + } + } + + /// Set the last commit hash + fn commit(env: Env, maintainer: Address, project_key: Bytes, hash: String) { + let key_ = types::ProjectKey::Key(project_key.clone()); + if let Some(project) = env + .storage() + .persistent() + .get::(&key_) + { + crate::auth_maintainers(&env, &maintainer, &project.maintainers); + env.storage() + .persistent() + .set(&types::ProjectKey::LastHash(project_key.clone()), &hash); + env.events() + .publish((symbol_short!("commit"), project_key), hash); + } else { + panic_with_error!(&env, &errors::ContractErrors::InvalidKey); + } + } + + /// Get the last commit hash + fn get_commit(env: Env, project_key: Bytes) -> String { + let key_ = types::ProjectKey::Key(project_key.clone()); + if env + .storage() + .persistent() + .get::(&key_) + .is_some() + { + env.storage() + .persistent() + .get(&types::ProjectKey::LastHash(project_key)) + .unwrap_or_else(|| { + panic_with_error!(&env, &errors::ContractErrors::NoHashFound); + }) + } else { + panic_with_error!(&env, &errors::ContractErrors::InvalidKey); + } + } + + fn get_project(env: Env, project_key: Bytes) -> types::Project { + let key_ = types::ProjectKey::Key(project_key.clone()); + + env.storage() + .persistent() + .get::(&key_) + .unwrap_or_else(|| { + panic_with_error!(&env, &errors::ContractErrors::InvalidKey); + }) + } +} + +/// Register a Soroban Domain: https://sorobandomains.org +pub fn domain_register(env: &Env, name: &Bytes, maintainer: &Address, domain_contract_id: Address) { + let tld = Bytes::from_slice(env, &[120, 108, 109]); // xlm + let min_duration: u64 = 31536000; + + // Convert the arguments to Val + let name_raw = name.to_val(); + let tld_raw = tld.to_val(); + let maintainer_raw = maintainer.to_val(); + let min_duration_raw: Val = min_duration.into_val(env); + + // Construct the init_args + let init_args = vec![ + &env, + name_raw, + tld_raw, + maintainer_raw, + maintainer_raw, + min_duration_raw, + ]; + + env.invoke_contract::<()>( + &domain_contract_id, + &Symbol::new(env, "set_record"), + init_args, + ); +} + +pub fn domain_node(env: &Env, domain: &Bytes) -> BytesN<32> { + let tld = Bytes::from_slice(env, &[120, 108, 109]); // xlm + let parent_hash: Bytes = env.crypto().keccak256(&tld).into(); + let mut node_builder: Bytes = Bytes::new(env); + node_builder.append(&parent_hash); + node_builder.append(domain); + + env.crypto().keccak256(&node_builder).into() +} diff --git a/contracts/versioning/src/errors.rs b/contracts/versioning/src/errors.rs new file mode 100644 index 00000000..c5a55c8f --- /dev/null +++ b/contracts/versioning/src/errors.rs @@ -0,0 +1,21 @@ +use soroban_sdk::contracterror; + +#[contracterror] +#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +#[repr(u32)] +pub enum ContractErrors { + UnexpectedError = 0, + // Versioning + InvalidKey = 1, + ProjectAlreadyExist = 2, + UnregisteredMaintainer = 3, + NoHashFound = 4, + // Soroban Domains + InvalidDomainError = 5, + MaintainerNotDomainOwner = 6, + // DAO + ProposalInputValidation = 7, + NoProposalorPageFound = 8, + AlreadyVoted = 9, + ProposalVotingTime = 10, +} diff --git a/contracts/versioning/src/lib.rs b/contracts/versioning/src/lib.rs index 9c9f7964..9003518b 100755 --- a/contracts/versioning/src/lib.rs +++ b/contracts/versioning/src/lib.rs @@ -1,77 +1,33 @@ #![no_std] +use soroban_sdk::contractmeta; +use soroban_sdk::{contract, panic_with_error, Address, Bytes, BytesN, Env, String, Vec}; + mod domain_contract { soroban_sdk::contractimport!( file = "../domain_3ebbeec072f4996958d4318656186732773ab5f0c159dcf039be202b4ecb8af8.wasm" ); } -use soroban_sdk::{ - contract, contractimpl, contracttype, panic_with_error, symbol_short, vec, Address, Bytes, - BytesN, Env, IntoVal, String, Symbol, Val, Vec, -}; -use soroban_sdk::{contracterror, contractmeta}; +mod contract_dao; +mod contract_versioning; +mod errors; +mod test; +mod types; contractmeta!(key = "Description", val = "Tansu - Soroban Versioning"); -#[contracterror] -#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] -pub enum ContractErrors { - UnexpectedError = 0, - InvalidKey = 1, - ProjectAlreadyExist = 2, - UnregisteredMaintainer = 3, - NoHashFound = 4, - InvalidDomainError = 5, - MaintainerNotDomainOwner = 6, -} - -#[contracttype] -pub enum DataKey { - Admin, // Contract administrator -} - -#[contracttype] -pub enum ProjectKey { - Key(Bytes), // UUID of the project from keccak256(name) - LastHash(Bytes), // last hash of the project -} - -#[contracttype] -pub struct Config { - pub url: String, // link to toml file with project metadata - pub hash: String, // hash of the file found at the URL -} - -#[contracttype] -pub struct Project { - pub name: String, - pub config: Config, - pub maintainers: Vec
, -} - #[contract] -pub struct Versioning; +pub struct Tansu; -#[contractimpl] -impl Versioning { - pub fn init(env: Env, admin: Address) { - env.storage().instance().set(&DataKey::Admin, &admin); - } +pub trait VersioningTrait { + fn init(env: Env, admin: Address); - pub fn version() -> u32 { - 1 - } + fn upgrade(env: Env, hash: BytesN<32>); - pub fn upgrade(env: Env, new_wasm_hash: BytesN<32>) { - let admin: Address = env.storage().instance().get(&DataKey::Admin).unwrap(); - admin.require_auth(); + fn version() -> u32; - env.deployer().update_current_contract_wasm(new_wasm_hash); - } - - /// Register a new Git projects and associated metadata. - pub fn register( + fn register( env: Env, maintainer: Address, name: String, @@ -79,167 +35,44 @@ impl Versioning { url: String, hash: String, domain_contract_id: Address, - ) -> Bytes { - let project = Project { - name: name.clone(), - config: Config { url, hash }, - maintainers, - }; - let str_len = name.len() as usize; - if str_len > 15 { - // could add more checks but handled in any case with later calls - panic_with_error!(&env, &ContractErrors::InvalidDomainError); - } - let mut slice: [u8; 15] = [0; 15]; - name.copy_into_slice(&mut slice[..str_len]); - let name_b = Bytes::from_slice(&env, &slice[0..str_len]); - - let key: Bytes = env.crypto().keccak256(&name_b).into(); - - let key_ = ProjectKey::Key(key.clone()); - if env - .storage() - .persistent() - .get::(&key_) - .is_some() - { - panic_with_error!(&env, &ContractErrors::ProjectAlreadyExist); - } else { - auth_maintainers(&env, &maintainer, &project.maintainers); - - let node = domain_node(&env, &key); - let record_keys = domain_contract::RecordKeys::Record(node); - - let domain_client = domain_contract::Client::new(&env, &domain_contract_id); - match domain_client.try_record(&record_keys) { - Ok(Ok(None)) => domain_register(&env, &name_b, &maintainer, domain_contract_id), - Ok(Ok(Some(domain_contract::Record::Domain(domain)))) => { - if domain.owner != maintainer { - panic_with_error!(&env, &ContractErrors::MaintainerNotDomainOwner) - } - } - _ => panic_with_error!(&env, &ContractErrors::InvalidDomainError), - } - env.storage().persistent().set(&key_, &project); - - env.events() - .publish((symbol_short!("register"), key.clone()), name); - key - } - } + ) -> Bytes; - /// Change the configuration of the project. - pub fn update_config( + fn update_config( env: Env, maintainer: Address, key: Bytes, maintainers: Vec
, url: String, hash: String, - ) { - let key_ = ProjectKey::Key(key); - if let Some(mut project) = env.storage().persistent().get::(&key_) { - auth_maintainers(&env, &maintainer, &project.maintainers); - - let config = Config { url, hash }; - project.config = config; - project.maintainers = maintainers; - env.storage().persistent().set(&key_, &project); - } else { - panic_with_error!(&env, &ContractErrors::InvalidKey); - } - } + ); - /// Set the last commit hash - pub fn commit(env: Env, maintainer: Address, project_key: Bytes, hash: String) { - let key_ = ProjectKey::Key(project_key.clone()); - if let Some(project) = env.storage().persistent().get::(&key_) { - auth_maintainers(&env, &maintainer, &project.maintainers); - env.storage() - .persistent() - .set(&ProjectKey::LastHash(project_key.clone()), &hash); - env.events() - .publish((symbol_short!("commit"), project_key), hash); - } else { - panic_with_error!(&env, &ContractErrors::InvalidKey); - } - } + fn commit(env: Env, maintainer: Address, project_key: Bytes, hash: String); - /// Get the last commit hash - pub fn get_commit(env: Env, project_key: Bytes) -> String { - let key_ = ProjectKey::Key(project_key.clone()); - if env - .storage() - .persistent() - .get::(&key_) - .is_some() - { - env.storage() - .persistent() - .get(&ProjectKey::LastHash(project_key)) - .unwrap_or_else(|| { - panic_with_error!(&env, &ContractErrors::NoHashFound); - }) - } else { - panic_with_error!(&env, &ContractErrors::InvalidKey); - } - } + fn get_commit(env: Env, project_key: Bytes) -> String; - pub fn get_project(env: Env, project_key: Bytes) -> Project { - let key_ = ProjectKey::Key(project_key.clone()); + fn get_project(env: Env, project_key: Bytes) -> types::Project; +} - env.storage() - .persistent() - .get::(&key_) - .unwrap_or_else(|| { - panic_with_error!(&env, &ContractErrors::InvalidKey); - }) - } +pub trait DaoTrait { + fn create_proposal( + env: Env, + proposer: Address, + project_key: Bytes, + title: String, + ipfs: String, + voting_ends_at: u64, + ) -> u32; + + fn vote(env: Env, voter: Address, project_key: Bytes, proposal_id: u32, vote: types::Vote); + + fn get_dao(env: Env, project_key: Bytes, page: u32) -> types::Dao; + + fn get_proposal(env: Env, project_key: Bytes, proposal_id: u32) -> types::Proposal; } fn auth_maintainers(env: &Env, maintainer: &Address, maintainers: &Vec
) { maintainer.require_auth(); if !maintainers.contains(maintainer) { - panic_with_error!(&env, &ContractErrors::UnregisteredMaintainer); + panic_with_error!(&env, &errors::ContractErrors::UnregisteredMaintainer); } } - -/// Register a Soroban Domain: https://sorobandomains.org -fn domain_register(env: &Env, name: &Bytes, maintainer: &Address, domain_contract_id: Address) { - let tld = Bytes::from_slice(env, &[120, 108, 109]); // xlm - let min_duration: u64 = 31536000; - - // Convert the arguments to Val - let name_raw = name.to_val(); - let tld_raw = tld.to_val(); - let maintainer_raw = maintainer.to_val(); - let min_duration_raw: Val = min_duration.into_val(env); - - // Construct the init_args - let init_args = vec![ - &env, - name_raw, - tld_raw, - maintainer_raw, - maintainer_raw, - min_duration_raw, - ]; - - env.invoke_contract::<()>( - &domain_contract_id, - &Symbol::new(env, "set_record"), - init_args, - ); -} - -fn domain_node(env: &Env, domain: &Bytes) -> BytesN<32> { - let tld = Bytes::from_slice(env, &[120, 108, 109]); // xlm - let parent_hash: Bytes = env.crypto().keccak256(&tld).into(); - let mut node_builder: Bytes = Bytes::new(env); - node_builder.append(&parent_hash); - node_builder.append(domain); - - env.crypto().keccak256(&node_builder).into() -} - -mod test; diff --git a/contracts/versioning/src/test.rs b/contracts/versioning/src/test.rs index abc4b33c..41f088e4 100755 --- a/contracts/versioning/src/test.rs +++ b/contracts/versioning/src/test.rs @@ -1,8 +1,9 @@ #![cfg(test)] -use super::{ - domain_contract, domain_node, domain_register, ContractErrors, Versioning, VersioningClient, -}; +use super::{domain_contract, Tansu, TansuClient}; +use crate::contract_versioning::{domain_node, domain_register}; +use crate::errors::ContractErrors; +use crate::types::{Dao, Vote}; use soroban_sdk::testutils::Address as _; use soroban_sdk::{ symbol_short, testutils::Events, token, vec, Address, Bytes, Env, IntoVal, String, Vec, @@ -44,8 +45,8 @@ fn test() { // setup for Tansu let contract_admin = Address::generate(&env); - let contract_id = env.register_contract(None, Versioning); - let contract = VersioningClient::new(&env, &contract_id); + let contract_id = env.register_contract(None, Tansu); + let contract = TansuClient::new(&env, &contract_id); contract.init(&contract_admin); @@ -171,6 +172,67 @@ fn test() { .unwrap(); assert_eq!(error, ContractErrors::MaintainerNotDomainOwner.into()); + + // DAO + let dao = contract.get_dao(&id, &0); + assert_eq!( + dao, + Dao { + proposals: Vec::new(&env) + } + ); + + let title = String::from_str(&env, "Integrate with xlm.sh"); + let ipfs = String::from_str( + &env, + "bafybeib6ioupho3p3pliusx7tgs7dvi6mpu2bwfhayj6w6ie44lo3vvc4i", + ); + let voting_ends_at = env.ledger().timestamp() + 3600 * 24 * 2; + let proposal_id = contract.create_proposal(&grogu, &id, &title, &ipfs, &voting_ends_at); + + assert_eq!(proposal_id, 0); + + let proposal = contract.get_proposal(&id, &proposal_id); + assert_eq!(proposal.id, 0); + assert_eq!(proposal.title, title); + assert_eq!(proposal.ipfs, ipfs); + assert_eq!(proposal.voting_ends_at, voting_ends_at); + assert_eq!(proposal.voters_approve, Vec::new(&env)); + assert_eq!(proposal.voters_reject, Vec::new(&env)); + assert_eq!(proposal.voters_abstain, vec![&env, grogu.clone()]); + + let dao = contract.get_dao(&id, &0); + assert_eq!( + dao, + Dao { + proposals: vec![&env, proposal.clone()] + } + ); + + // id does not exist + let error = contract.try_get_proposal(&id, &10).unwrap_err().unwrap(); + assert_eq!(error, ContractErrors::NoProposalorPageFound.into()); + + // cannot vote for your own proposal + let error = contract + .try_vote(&grogu, &id, &proposal_id, &Vote::Approve) + .unwrap_err() + .unwrap(); + assert_eq!(error, ContractErrors::AlreadyVoted.into()); + + contract.vote(&mando, &id, &proposal_id, &Vote::Approve); + + // cannot vote twice + let error = contract + .try_vote(&mando, &id, &proposal_id, &Vote::Approve) + .unwrap_err() + .unwrap(); + assert_eq!(error, ContractErrors::AlreadyVoted.into()); + + let proposal = contract.get_proposal(&id, &proposal_id); + assert_eq!(proposal.voters_approve, vec![&env, mando.clone()]); + assert_eq!(proposal.voters_reject, Vec::new(&env)); + assert_eq!(proposal.voters_abstain, vec![&env, grogu.clone()]); } #[test] diff --git a/contracts/versioning/src/types.rs b/contracts/versioning/src/types.rs new file mode 100644 index 00000000..e2b4bff7 --- /dev/null +++ b/contracts/versioning/src/types.rs @@ -0,0 +1,67 @@ +use soroban_sdk::{contracttype, Address, Bytes, String, Vec}; + +#[contracttype] +pub enum DataKey { + Admin, // Contract administrator +} + +#[contracttype] +#[derive(Clone, Debug, PartialEq)] +pub enum ProposalStatus { + Active, + Approved, + Rejected, + Cancelled, +} + +#[contracttype] +#[derive(Clone, Debug, PartialEq)] +pub enum Vote { + Approve, + Reject, + Abstain, +} + +#[contracttype] +#[derive(Clone, Debug, PartialEq)] +pub struct Proposal { + pub id: u32, + pub title: String, + pub ipfs: String, + pub voting_ends_at: u64, + pub voters_approve: Vec
, + pub voters_reject: Vec
, + pub voters_abstain: Vec
, + pub nqg: u32, + pub status: ProposalStatus, +} + +#[contracttype] +#[derive(Clone, Debug, PartialEq)] +pub struct Dao { + pub proposals: Vec, +} + +#[contracttype] +#[derive(Clone, Debug, PartialEq)] +pub enum ProjectKey { + Key(Bytes), // UUID of the project from keccak256(name) + LastHash(Bytes), // last hash of the project + Dao(Bytes, u32), // Decentralized organization, pagination + DaoTotalProposals(Bytes), +} + +#[contracttype] +#[derive(Clone, Debug, PartialEq)] +pub struct Config { + pub url: String, // link to toml file with project metadata + pub hash: String, // hash of the file found at the URL +} + +#[contracttype] +#[derive(Clone, Debug, PartialEq)] +pub struct Project { + pub name: String, + pub config: Config, + pub maintainers: Vec
, +} diff --git a/dapp/packages/soroban_versioning/dist/index.d.ts b/dapp/packages/soroban_versioning/dist/index.d.ts index ead25b74..85be5e82 100644 --- a/dapp/packages/soroban_versioning/dist/index.d.ts +++ b/dapp/packages/soroban_versioning/dist/index.d.ts @@ -4,7 +4,7 @@ import { Client as ContractClient, ClientOptions as ContractClientOptions, } from "@stellar/stellar-sdk/contract"; -import type { u32 } from "@stellar/stellar-sdk/contract"; +import type { u32, u64 } from "@stellar/stellar-sdk/contract"; export * from "@stellar/stellar-sdk"; export * as contract from "@stellar/stellar-sdk/contract"; export * as rpc from "@stellar/stellar-sdk/rpc"; @@ -36,11 +36,61 @@ export declare const Errors: { 6: { message: string; }; + 7: { + message: string; + }; + 8: { + message: string; + }; }; export type DataKey = { tag: "Admin"; values: void; }; +export type ProposalStatus = + | { + tag: "Active"; + values: void; + } + | { + tag: "Approved"; + values: void; + } + | { + tag: "Rejected"; + values: void; + } + | { + tag: "Cancelled"; + values: void; + }; +export type Vote = + | { + tag: "Approve"; + values: void; + } + | { + tag: "Reject"; + values: void; + } + | { + tag: "Abstain"; + values: void; + }; +export interface Proposal { + id: u32; + ipfs: string; + nqg: u32; + status: ProposalStatus; + title: string; + voters_abstain: Array; + voters_approve: Array; + voters_reject: Array; + voting_ends_at: u64; +} +export interface Dao { + proposals: Array; +} export type ProjectKey = | { tag: "Key"; @@ -49,6 +99,14 @@ export type ProjectKey = | { tag: "LastHash"; values: readonly [Buffer]; + } + | { + tag: "Dao"; + values: readonly [Buffer, u32]; + } + | { + tag: "DaoTotalProposals"; + values: readonly [Buffer]; }; export interface Config { hash: string; @@ -60,6 +118,127 @@ export interface Project { name: string; } export interface Client { + /** + * Construct and simulate a create_proposal transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. + * Create a proposal on the DAO of the project. + * Proposal initiators are automatically put in the abstain group. + */ + create_proposal: ( + { + proposer, + project_key, + title, + ipfs, + voting_ends_at, + }: { + proposer: string; + project_key: Buffer; + title: string; + ipfs: string; + voting_ends_at: u64; + }, + options?: { + /** + * The fee to pay for the transaction. Default: BASE_FEE + */ + fee?: number; + /** + * The maximum amount of time to wait for the transaction to complete. Default: DEFAULT_TIMEOUT + */ + timeoutInSeconds?: number; + /** + * Whether to automatically simulate the transaction when constructing the AssembledTransaction. Default: true + */ + simulate?: boolean; + }, + ) => Promise>; + /** + * Construct and simulate a vote transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. + * Cast a vote on a proposal. + * Double votes are not allowed. + */ + vote: ( + { + voter, + project_key, + proposal_id, + vote, + }: { + voter: string; + project_key: Buffer; + proposal_id: u32; + vote: Vote; + }, + options?: { + /** + * The fee to pay for the transaction. Default: BASE_FEE + */ + fee?: number; + /** + * The maximum amount of time to wait for the transaction to complete. Default: DEFAULT_TIMEOUT + */ + timeoutInSeconds?: number; + /** + * Whether to automatically simulate the transaction when constructing the AssembledTransaction. Default: true + */ + simulate?: boolean; + }, + ) => Promise>; + /** + * Construct and simulate a get_dao transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. + * Get one page of proposal of the DAO. + * A page has 10 proposals. + */ + get_dao: ( + { + project_key, + page, + }: { + project_key: Buffer; + page: u32; + }, + options?: { + /** + * The fee to pay for the transaction. Default: BASE_FEE + */ + fee?: number; + /** + * The maximum amount of time to wait for the transaction to complete. Default: DEFAULT_TIMEOUT + */ + timeoutInSeconds?: number; + /** + * Whether to automatically simulate the transaction when constructing the AssembledTransaction. Default: true + */ + simulate?: boolean; + }, + ) => Promise>; + /** + * Construct and simulate a get_proposal transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. + * Only return a single proposal + */ + get_proposal: ( + { + project_key, + proposal_id, + }: { + project_key: Buffer; + proposal_id: u32; + }, + options?: { + /** + * The fee to pay for the transaction. Default: BASE_FEE + */ + fee?: number; + /** + * The maximum amount of time to wait for the transaction to complete. Default: DEFAULT_TIMEOUT + */ + timeoutInSeconds?: number; + /** + * Whether to automatically simulate the transaction when constructing the AssembledTransaction. Default: true + */ + simulate?: boolean; + }, + ) => Promise>; /** * Construct and simulate a init transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. */ @@ -84,23 +263,6 @@ export interface Client { simulate?: boolean; }, ) => Promise>; - /** - * Construct and simulate a version transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. - */ - version: (options?: { - /** - * The fee to pay for the transaction. Default: BASE_FEE - */ - fee?: number; - /** - * The maximum amount of time to wait for the transaction to complete. Default: DEFAULT_TIMEOUT - */ - timeoutInSeconds?: number; - /** - * Whether to automatically simulate the transaction when constructing the AssembledTransaction. Default: true - */ - simulate?: boolean; - }) => Promise>; /** * Construct and simulate a upgrade transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. */ @@ -125,6 +287,23 @@ export interface Client { simulate?: boolean; }, ) => Promise>; + /** + * Construct and simulate a version transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. + */ + version: (options?: { + /** + * The fee to pay for the transaction. Default: BASE_FEE + */ + fee?: number; + /** + * The maximum amount of time to wait for the transaction to complete. Default: DEFAULT_TIMEOUT + */ + timeoutInSeconds?: number; + /** + * Whether to automatically simulate the transaction when constructing the AssembledTransaction. Default: true + */ + simulate?: boolean; + }) => Promise>; /** * Construct and simulate a register transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. * Register a new Git projects and associated metadata. @@ -276,9 +455,13 @@ export declare class Client extends ContractClient { readonly options: ContractClientOptions; constructor(options: ContractClientOptions); readonly fromJSON: { + create_proposal: (json: string) => AssembledTransaction; + vote: (json: string) => AssembledTransaction; + get_dao: (json: string) => AssembledTransaction; + get_proposal: (json: string) => AssembledTransaction; init: (json: string) => AssembledTransaction; - version: (json: string) => AssembledTransaction; upgrade: (json: string) => AssembledTransaction; + version: (json: string) => AssembledTransaction; register: (json: string) => AssembledTransaction; update_config: (json: string) => AssembledTransaction; commit: (json: string) => AssembledTransaction; diff --git a/dapp/packages/soroban_versioning/dist/index.js b/dapp/packages/soroban_versioning/dist/index.js index b4b428ee..9be01540 100644 --- a/dapp/packages/soroban_versioning/dist/index.js +++ b/dapp/packages/soroban_versioning/dist/index.js @@ -24,34 +24,48 @@ export const Errors = { 4: { message: "NoHashFound" }, 5: { message: "InvalidDomainError" }, 6: { message: "MaintainerNotDomainOwner" }, + 7: { message: "NoProposalorPageFound" }, + 8: { message: "AlreadyVoted" }, }; export class Client extends ContractClient { options; constructor(options) { super( new ContractSpec([ - "AAAABAAAAAAAAAAAAAAADkNvbnRyYWN0RXJyb3JzAAAAAAAHAAAAAAAAAA9VbmV4cGVjdGVkRXJyb3IAAAAAAAAAAAAAAAAKSW52YWxpZEtleQAAAAAAAQAAAAAAAAATUHJvamVjdEFscmVhZHlFeGlzdAAAAAACAAAAAAAAABZVbnJlZ2lzdGVyZWRNYWludGFpbmVyAAAAAAADAAAAAAAAAAtOb0hhc2hGb3VuZAAAAAAEAAAAAAAAABJJbnZhbGlkRG9tYWluRXJyb3IAAAAAAAUAAAAAAAAAGE1haW50YWluZXJOb3REb21haW5Pd25lcgAAAAY=", - "AAAAAgAAAAAAAAAAAAAAB0RhdGFLZXkAAAAAAQAAAAAAAAAAAAAABUFkbWluAAAA", - "AAAAAgAAAAAAAAAAAAAAClByb2plY3RLZXkAAAAAAAIAAAABAAAAAAAAAANLZXkAAAAAAQAAAA4AAAABAAAAAAAAAAhMYXN0SGFzaAAAAAEAAAAO", - "AAAAAQAAAAAAAAAAAAAABkNvbmZpZwAAAAAAAgAAAAAAAAAEaGFzaAAAABAAAAAAAAAAA3VybAAAAAAQ", - "AAAAAQAAAAAAAAAAAAAAB1Byb2plY3QAAAAAAwAAAAAAAAAGY29uZmlnAAAAAAfQAAAABkNvbmZpZwAAAAAAAAAAAAttYWludGFpbmVycwAAAAPqAAAAEwAAAAAAAAAEbmFtZQAAABA=", + "AAAAAAAAAGxDcmVhdGUgYSBwcm9wb3NhbCBvbiB0aGUgREFPIG9mIHRoZSBwcm9qZWN0LgpQcm9wb3NhbCBpbml0aWF0b3JzIGFyZSBhdXRvbWF0aWNhbGx5IHB1dCBpbiB0aGUgYWJzdGFpbiBncm91cC4AAAAPY3JlYXRlX3Byb3Bvc2FsAAAAAAUAAAAAAAAACHByb3Bvc2VyAAAAEwAAAAAAAAALcHJvamVjdF9rZXkAAAAADgAAAAAAAAAFdGl0bGUAAAAAAAAQAAAAAAAAAARpcGZzAAAAEAAAAAAAAAAOdm90aW5nX2VuZHNfYXQAAAAAAAYAAAABAAAABA==", + "AAAAAAAAADhDYXN0IGEgdm90ZSBvbiBhIHByb3Bvc2FsLgpEb3VibGUgdm90ZXMgYXJlIG5vdCBhbGxvd2VkLgAAAAR2b3RlAAAABAAAAAAAAAAFdm90ZXIAAAAAAAATAAAAAAAAAAtwcm9qZWN0X2tleQAAAAAOAAAAAAAAAAtwcm9wb3NhbF9pZAAAAAAEAAAAAAAAAAR2b3RlAAAH0AAAAARWb3RlAAAAAA==", + "AAAAAAAAAD1HZXQgb25lIHBhZ2Ugb2YgcHJvcG9zYWwgb2YgdGhlIERBTy4KQSBwYWdlIGhhcyAxMCBwcm9wb3NhbHMuAAAAAAAAB2dldF9kYW8AAAAAAgAAAAAAAAALcHJvamVjdF9rZXkAAAAADgAAAAAAAAAEcGFnZQAAAAQAAAABAAAH0AAAAANEYW8A", + "AAAAAAAAAB1Pbmx5IHJldHVybiBhIHNpbmdsZSBwcm9wb3NhbAAAAAAAAAxnZXRfcHJvcG9zYWwAAAACAAAAAAAAAAtwcm9qZWN0X2tleQAAAAAOAAAAAAAAAAtwcm9wb3NhbF9pZAAAAAAEAAAAAQAAB9AAAAAIUHJvcG9zYWw=", "AAAAAAAAAAAAAAAEaW5pdAAAAAEAAAAAAAAABWFkbWluAAAAAAAAEwAAAAA=", - "AAAAAAAAAAAAAAAHdmVyc2lvbgAAAAAAAAAAAQAAAAQ=", "AAAAAAAAAAAAAAAHdXBncmFkZQAAAAABAAAAAAAAAA1uZXdfd2FzbV9oYXNoAAAAAAAD7gAAACAAAAAA", + "AAAAAAAAAAAAAAAHdmVyc2lvbgAAAAAAAAAAAQAAAAQ=", "AAAAAAAAADRSZWdpc3RlciBhIG5ldyBHaXQgcHJvamVjdHMgYW5kIGFzc29jaWF0ZWQgbWV0YWRhdGEuAAAACHJlZ2lzdGVyAAAABgAAAAAAAAAKbWFpbnRhaW5lcgAAAAAAEwAAAAAAAAAEbmFtZQAAABAAAAAAAAAAC21haW50YWluZXJzAAAAA+oAAAATAAAAAAAAAAN1cmwAAAAAEAAAAAAAAAAEaGFzaAAAABAAAAAAAAAAEmRvbWFpbl9jb250cmFjdF9pZAAAAAAAEwAAAAEAAAAO", "AAAAAAAAAChDaGFuZ2UgdGhlIGNvbmZpZ3VyYXRpb24gb2YgdGhlIHByb2plY3QuAAAADXVwZGF0ZV9jb25maWcAAAAAAAAFAAAAAAAAAAptYWludGFpbmVyAAAAAAATAAAAAAAAAANrZXkAAAAADgAAAAAAAAALbWFpbnRhaW5lcnMAAAAD6gAAABMAAAAAAAAAA3VybAAAAAAQAAAAAAAAAARoYXNoAAAAEAAAAAA=", "AAAAAAAAABhTZXQgdGhlIGxhc3QgY29tbWl0IGhhc2gAAAAGY29tbWl0AAAAAAADAAAAAAAAAAptYWludGFpbmVyAAAAAAATAAAAAAAAAAtwcm9qZWN0X2tleQAAAAAOAAAAAAAAAARoYXNoAAAAEAAAAAA=", "AAAAAAAAABhHZXQgdGhlIGxhc3QgY29tbWl0IGhhc2gAAAAKZ2V0X2NvbW1pdAAAAAAAAQAAAAAAAAALcHJvamVjdF9rZXkAAAAADgAAAAEAAAAQ", "AAAAAAAAAAAAAAALZ2V0X3Byb2plY3QAAAAAAQAAAAAAAAALcHJvamVjdF9rZXkAAAAADgAAAAEAAAfQAAAAB1Byb2plY3QA", + "AAAABAAAAAAAAAAAAAAADkNvbnRyYWN0RXJyb3JzAAAAAAAJAAAAAAAAAA9VbmV4cGVjdGVkRXJyb3IAAAAAAAAAAAAAAAAKSW52YWxpZEtleQAAAAAAAQAAAAAAAAATUHJvamVjdEFscmVhZHlFeGlzdAAAAAACAAAAAAAAABZVbnJlZ2lzdGVyZWRNYWludGFpbmVyAAAAAAADAAAAAAAAAAtOb0hhc2hGb3VuZAAAAAAEAAAAAAAAABJJbnZhbGlkRG9tYWluRXJyb3IAAAAAAAUAAAAAAAAAGE1haW50YWluZXJOb3REb21haW5Pd25lcgAAAAYAAAAAAAAAFU5vUHJvcG9zYWxvclBhZ2VGb3VuZAAAAAAAAAcAAAAAAAAADEFscmVhZHlWb3RlZAAAAAg=", + "AAAAAgAAAAAAAAAAAAAAB0RhdGFLZXkAAAAAAQAAAAAAAAAAAAAABUFkbWluAAAA", + "AAAAAgAAAAAAAAAAAAAADlByb3Bvc2FsU3RhdHVzAAAAAAAEAAAAAAAAAAAAAAAGQWN0aXZlAAAAAAAAAAAAAAAAAAhBcHByb3ZlZAAAAAAAAAAAAAAACFJlamVjdGVkAAAAAAAAAAAAAAAJQ2FuY2VsbGVkAAAA", + "AAAAAgAAAAAAAAAAAAAABFZvdGUAAAADAAAAAAAAAAAAAAAHQXBwcm92ZQAAAAAAAAAAAAAAAAZSZWplY3QAAAAAAAAAAAAAAAAAB0Fic3RhaW4A", + "AAAAAQAAAAAAAAAAAAAACFByb3Bvc2FsAAAACQAAAAAAAAACaWQAAAAAAAQAAAAAAAAABGlwZnMAAAAQAAAAAAAAAANucWcAAAAABAAAAAAAAAAGc3RhdHVzAAAAAAfQAAAADlByb3Bvc2FsU3RhdHVzAAAAAAAAAAAABXRpdGxlAAAAAAAAEAAAAAAAAAAOdm90ZXJzX2Fic3RhaW4AAAAAA+oAAAATAAAAAAAAAA52b3RlcnNfYXBwcm92ZQAAAAAD6gAAABMAAAAAAAAADXZvdGVyc19yZWplY3QAAAAAAAPqAAAAEwAAAAAAAAAOdm90aW5nX2VuZHNfYXQAAAAAAAY=", + "AAAAAQAAAAAAAAAAAAAAA0RhbwAAAAABAAAAAAAAAAlwcm9wb3NhbHMAAAAAAAPqAAAH0AAAAAhQcm9wb3NhbA==", + "AAAAAgAAAAAAAAAAAAAAClByb2plY3RLZXkAAAAAAAQAAAABAAAAAAAAAANLZXkAAAAAAQAAAA4AAAABAAAAAAAAAAhMYXN0SGFzaAAAAAEAAAAOAAAAAQAAAAAAAAADRGFvAAAAAAIAAAAOAAAABAAAAAEAAAAAAAAAEURhb1RvdGFsUHJvcG9zYWxzAAAAAAAAAQAAAA4=", + "AAAAAQAAAAAAAAAAAAAABkNvbmZpZwAAAAAAAgAAAAAAAAAEaGFzaAAAABAAAAAAAAAAA3VybAAAAAAQ", + "AAAAAQAAAAAAAAAAAAAAB1Byb2plY3QAAAAAAwAAAAAAAAAGY29uZmlnAAAAAAfQAAAABkNvbmZpZwAAAAAAAAAAAAttYWludGFpbmVycwAAAAPqAAAAEwAAAAAAAAAEbmFtZQAAABA=", ]), options, ); this.options = options; } fromJSON = { + create_proposal: this.txFromJSON, + vote: this.txFromJSON, + get_dao: this.txFromJSON, + get_proposal: this.txFromJSON, init: this.txFromJSON, - version: this.txFromJSON, upgrade: this.txFromJSON, + version: this.txFromJSON, register: this.txFromJSON, update_config: this.txFromJSON, commit: this.txFromJSON, diff --git a/dapp/packages/soroban_versioning/src/index.ts b/dapp/packages/soroban_versioning/src/index.ts index 19040109..b1da86a2 100644 --- a/dapp/packages/soroban_versioning/src/index.ts +++ b/dapp/packages/soroban_versioning/src/index.ts @@ -50,12 +50,45 @@ export const Errors = { 5: { message: "InvalidDomainError" }, 6: { message: "MaintainerNotDomainOwner" }, + + 7: { message: "NoProposalorPageFound" }, + + 8: { message: "AlreadyVoted" }, }; export type DataKey = { tag: "Admin"; values: void }; +export type ProposalStatus = + | { tag: "Active"; values: void } + | { tag: "Approved"; values: void } + | { tag: "Rejected"; values: void } + | { tag: "Cancelled"; values: void }; + +export type Vote = + | { tag: "Approve"; values: void } + | { tag: "Reject"; values: void } + | { tag: "Abstain"; values: void }; + +export interface Proposal { + id: u32; + ipfs: string; + nqg: u32; + status: ProposalStatus; + title: string; + voters_abstain: Array; + voters_approve: Array; + voters_reject: Array; + voting_ends_at: u64; +} + +export interface Dao { + proposals: Array; +} + export type ProjectKey = | { tag: "Key"; values: readonly [Buffer] } - | { tag: "LastHash"; values: readonly [Buffer] }; + | { tag: "LastHash"; values: readonly [Buffer] } + | { tag: "Dao"; values: readonly [Buffer, u32] } + | { tag: "DaoTotalProposals"; values: readonly [Buffer] }; export interface Config { hash: string; @@ -70,10 +103,54 @@ export interface Project { export interface Client { /** - * Construct and simulate a init transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. + * Construct and simulate a create_proposal transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. + * Create a proposal on the DAO of the project. + * Proposal initiators are automatically put in the abstain group. */ - init: ( - { admin }: { admin: string }, + create_proposal: ( + { + proposer, + project_key, + title, + ipfs, + voting_ends_at, + }: { + proposer: string; + project_key: Buffer; + title: string; + ipfs: string; + voting_ends_at: u64; + }, + options?: { + /** + * The fee to pay for the transaction. Default: BASE_FEE + */ + fee?: number; + + /** + * The maximum amount of time to wait for the transaction to complete. Default: DEFAULT_TIMEOUT + */ + timeoutInSeconds?: number; + + /** + * Whether to automatically simulate the transaction when constructing the AssembledTransaction. Default: true + */ + simulate?: boolean; + }, + ) => Promise>; + + /** + * Construct and simulate a vote transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. + * Cast a vote on a proposal. + * Double votes are not allowed. + */ + vote: ( + { + voter, + project_key, + proposal_id, + vote, + }: { voter: string; project_key: Buffer; proposal_id: u32; vote: Vote }, options?: { /** * The fee to pay for the transaction. Default: BASE_FEE @@ -93,24 +170,76 @@ export interface Client { ) => Promise>; /** - * Construct and simulate a version transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. + * Construct and simulate a get_dao transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. + * Get one page of proposal of the DAO. + * A page has 10 proposals. */ - version: (options?: { - /** - * The fee to pay for the transaction. Default: BASE_FEE - */ - fee?: number; + get_dao: ( + { project_key, page }: { project_key: Buffer; page: u32 }, + options?: { + /** + * The fee to pay for the transaction. Default: BASE_FEE + */ + fee?: number; - /** - * The maximum amount of time to wait for the transaction to complete. Default: DEFAULT_TIMEOUT - */ - timeoutInSeconds?: number; + /** + * The maximum amount of time to wait for the transaction to complete. Default: DEFAULT_TIMEOUT + */ + timeoutInSeconds?: number; - /** - * Whether to automatically simulate the transaction when constructing the AssembledTransaction. Default: true - */ - simulate?: boolean; - }) => Promise>; + /** + * Whether to automatically simulate the transaction when constructing the AssembledTransaction. Default: true + */ + simulate?: boolean; + }, + ) => Promise>; + + /** + * Construct and simulate a get_proposal transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. + * Only return a single proposal + */ + get_proposal: ( + { project_key, proposal_id }: { project_key: Buffer; proposal_id: u32 }, + options?: { + /** + * The fee to pay for the transaction. Default: BASE_FEE + */ + fee?: number; + + /** + * The maximum amount of time to wait for the transaction to complete. Default: DEFAULT_TIMEOUT + */ + timeoutInSeconds?: number; + + /** + * Whether to automatically simulate the transaction when constructing the AssembledTransaction. Default: true + */ + simulate?: boolean; + }, + ) => Promise>; + + /** + * Construct and simulate a init transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. + */ + init: ( + { admin }: { admin: string }, + options?: { + /** + * The fee to pay for the transaction. Default: BASE_FEE + */ + fee?: number; + + /** + * The maximum amount of time to wait for the transaction to complete. Default: DEFAULT_TIMEOUT + */ + timeoutInSeconds?: number; + + /** + * Whether to automatically simulate the transaction when constructing the AssembledTransaction. Default: true + */ + simulate?: boolean; + }, + ) => Promise>; /** * Construct and simulate a upgrade transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. @@ -135,6 +264,26 @@ export interface Client { }, ) => Promise>; + /** + * Construct and simulate a version transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. + */ + version: (options?: { + /** + * The fee to pay for the transaction. Default: BASE_FEE + */ + fee?: number; + + /** + * The maximum amount of time to wait for the transaction to complete. Default: DEFAULT_TIMEOUT + */ + timeoutInSeconds?: number; + + /** + * Whether to automatically simulate the transaction when constructing the AssembledTransaction. Default: true + */ + simulate?: boolean; + }) => Promise>; + /** * Construct and simulate a register transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. * Register a new Git projects and associated metadata. @@ -288,27 +437,39 @@ export class Client extends ContractClient { constructor(public readonly options: ContractClientOptions) { super( new ContractSpec([ - "AAAABAAAAAAAAAAAAAAADkNvbnRyYWN0RXJyb3JzAAAAAAAHAAAAAAAAAA9VbmV4cGVjdGVkRXJyb3IAAAAAAAAAAAAAAAAKSW52YWxpZEtleQAAAAAAAQAAAAAAAAATUHJvamVjdEFscmVhZHlFeGlzdAAAAAACAAAAAAAAABZVbnJlZ2lzdGVyZWRNYWludGFpbmVyAAAAAAADAAAAAAAAAAtOb0hhc2hGb3VuZAAAAAAEAAAAAAAAABJJbnZhbGlkRG9tYWluRXJyb3IAAAAAAAUAAAAAAAAAGE1haW50YWluZXJOb3REb21haW5Pd25lcgAAAAY=", - "AAAAAgAAAAAAAAAAAAAAB0RhdGFLZXkAAAAAAQAAAAAAAAAAAAAABUFkbWluAAAA", - "AAAAAgAAAAAAAAAAAAAAClByb2plY3RLZXkAAAAAAAIAAAABAAAAAAAAAANLZXkAAAAAAQAAAA4AAAABAAAAAAAAAAhMYXN0SGFzaAAAAAEAAAAO", - "AAAAAQAAAAAAAAAAAAAABkNvbmZpZwAAAAAAAgAAAAAAAAAEaGFzaAAAABAAAAAAAAAAA3VybAAAAAAQ", - "AAAAAQAAAAAAAAAAAAAAB1Byb2plY3QAAAAAAwAAAAAAAAAGY29uZmlnAAAAAAfQAAAABkNvbmZpZwAAAAAAAAAAAAttYWludGFpbmVycwAAAAPqAAAAEwAAAAAAAAAEbmFtZQAAABA=", + "AAAAAAAAAGxDcmVhdGUgYSBwcm9wb3NhbCBvbiB0aGUgREFPIG9mIHRoZSBwcm9qZWN0LgpQcm9wb3NhbCBpbml0aWF0b3JzIGFyZSBhdXRvbWF0aWNhbGx5IHB1dCBpbiB0aGUgYWJzdGFpbiBncm91cC4AAAAPY3JlYXRlX3Byb3Bvc2FsAAAAAAUAAAAAAAAACHByb3Bvc2VyAAAAEwAAAAAAAAALcHJvamVjdF9rZXkAAAAADgAAAAAAAAAFdGl0bGUAAAAAAAAQAAAAAAAAAARpcGZzAAAAEAAAAAAAAAAOdm90aW5nX2VuZHNfYXQAAAAAAAYAAAABAAAABA==", + "AAAAAAAAADhDYXN0IGEgdm90ZSBvbiBhIHByb3Bvc2FsLgpEb3VibGUgdm90ZXMgYXJlIG5vdCBhbGxvd2VkLgAAAAR2b3RlAAAABAAAAAAAAAAFdm90ZXIAAAAAAAATAAAAAAAAAAtwcm9qZWN0X2tleQAAAAAOAAAAAAAAAAtwcm9wb3NhbF9pZAAAAAAEAAAAAAAAAAR2b3RlAAAH0AAAAARWb3RlAAAAAA==", + "AAAAAAAAAD1HZXQgb25lIHBhZ2Ugb2YgcHJvcG9zYWwgb2YgdGhlIERBTy4KQSBwYWdlIGhhcyAxMCBwcm9wb3NhbHMuAAAAAAAAB2dldF9kYW8AAAAAAgAAAAAAAAALcHJvamVjdF9rZXkAAAAADgAAAAAAAAAEcGFnZQAAAAQAAAABAAAH0AAAAANEYW8A", + "AAAAAAAAAB1Pbmx5IHJldHVybiBhIHNpbmdsZSBwcm9wb3NhbAAAAAAAAAxnZXRfcHJvcG9zYWwAAAACAAAAAAAAAAtwcm9qZWN0X2tleQAAAAAOAAAAAAAAAAtwcm9wb3NhbF9pZAAAAAAEAAAAAQAAB9AAAAAIUHJvcG9zYWw=", "AAAAAAAAAAAAAAAEaW5pdAAAAAEAAAAAAAAABWFkbWluAAAAAAAAEwAAAAA=", - "AAAAAAAAAAAAAAAHdmVyc2lvbgAAAAAAAAAAAQAAAAQ=", "AAAAAAAAAAAAAAAHdXBncmFkZQAAAAABAAAAAAAAAA1uZXdfd2FzbV9oYXNoAAAAAAAD7gAAACAAAAAA", + "AAAAAAAAAAAAAAAHdmVyc2lvbgAAAAAAAAAAAQAAAAQ=", "AAAAAAAAADRSZWdpc3RlciBhIG5ldyBHaXQgcHJvamVjdHMgYW5kIGFzc29jaWF0ZWQgbWV0YWRhdGEuAAAACHJlZ2lzdGVyAAAABgAAAAAAAAAKbWFpbnRhaW5lcgAAAAAAEwAAAAAAAAAEbmFtZQAAABAAAAAAAAAAC21haW50YWluZXJzAAAAA+oAAAATAAAAAAAAAAN1cmwAAAAAEAAAAAAAAAAEaGFzaAAAABAAAAAAAAAAEmRvbWFpbl9jb250cmFjdF9pZAAAAAAAEwAAAAEAAAAO", "AAAAAAAAAChDaGFuZ2UgdGhlIGNvbmZpZ3VyYXRpb24gb2YgdGhlIHByb2plY3QuAAAADXVwZGF0ZV9jb25maWcAAAAAAAAFAAAAAAAAAAptYWludGFpbmVyAAAAAAATAAAAAAAAAANrZXkAAAAADgAAAAAAAAALbWFpbnRhaW5lcnMAAAAD6gAAABMAAAAAAAAAA3VybAAAAAAQAAAAAAAAAARoYXNoAAAAEAAAAAA=", "AAAAAAAAABhTZXQgdGhlIGxhc3QgY29tbWl0IGhhc2gAAAAGY29tbWl0AAAAAAADAAAAAAAAAAptYWludGFpbmVyAAAAAAATAAAAAAAAAAtwcm9qZWN0X2tleQAAAAAOAAAAAAAAAARoYXNoAAAAEAAAAAA=", "AAAAAAAAABhHZXQgdGhlIGxhc3QgY29tbWl0IGhhc2gAAAAKZ2V0X2NvbW1pdAAAAAAAAQAAAAAAAAALcHJvamVjdF9rZXkAAAAADgAAAAEAAAAQ", "AAAAAAAAAAAAAAALZ2V0X3Byb2plY3QAAAAAAQAAAAAAAAALcHJvamVjdF9rZXkAAAAADgAAAAEAAAfQAAAAB1Byb2plY3QA", + "AAAABAAAAAAAAAAAAAAADkNvbnRyYWN0RXJyb3JzAAAAAAAJAAAAAAAAAA9VbmV4cGVjdGVkRXJyb3IAAAAAAAAAAAAAAAAKSW52YWxpZEtleQAAAAAAAQAAAAAAAAATUHJvamVjdEFscmVhZHlFeGlzdAAAAAACAAAAAAAAABZVbnJlZ2lzdGVyZWRNYWludGFpbmVyAAAAAAADAAAAAAAAAAtOb0hhc2hGb3VuZAAAAAAEAAAAAAAAABJJbnZhbGlkRG9tYWluRXJyb3IAAAAAAAUAAAAAAAAAGE1haW50YWluZXJOb3REb21haW5Pd25lcgAAAAYAAAAAAAAAFU5vUHJvcG9zYWxvclBhZ2VGb3VuZAAAAAAAAAcAAAAAAAAADEFscmVhZHlWb3RlZAAAAAg=", + "AAAAAgAAAAAAAAAAAAAAB0RhdGFLZXkAAAAAAQAAAAAAAAAAAAAABUFkbWluAAAA", + "AAAAAgAAAAAAAAAAAAAADlByb3Bvc2FsU3RhdHVzAAAAAAAEAAAAAAAAAAAAAAAGQWN0aXZlAAAAAAAAAAAAAAAAAAhBcHByb3ZlZAAAAAAAAAAAAAAACFJlamVjdGVkAAAAAAAAAAAAAAAJQ2FuY2VsbGVkAAAA", + "AAAAAgAAAAAAAAAAAAAABFZvdGUAAAADAAAAAAAAAAAAAAAHQXBwcm92ZQAAAAAAAAAAAAAAAAZSZWplY3QAAAAAAAAAAAAAAAAAB0Fic3RhaW4A", + "AAAAAQAAAAAAAAAAAAAACFByb3Bvc2FsAAAACQAAAAAAAAACaWQAAAAAAAQAAAAAAAAABGlwZnMAAAAQAAAAAAAAAANucWcAAAAABAAAAAAAAAAGc3RhdHVzAAAAAAfQAAAADlByb3Bvc2FsU3RhdHVzAAAAAAAAAAAABXRpdGxlAAAAAAAAEAAAAAAAAAAOdm90ZXJzX2Fic3RhaW4AAAAAA+oAAAATAAAAAAAAAA52b3RlcnNfYXBwcm92ZQAAAAAD6gAAABMAAAAAAAAADXZvdGVyc19yZWplY3QAAAAAAAPqAAAAEwAAAAAAAAAOdm90aW5nX2VuZHNfYXQAAAAAAAY=", + "AAAAAQAAAAAAAAAAAAAAA0RhbwAAAAABAAAAAAAAAAlwcm9wb3NhbHMAAAAAAAPqAAAH0AAAAAhQcm9wb3NhbA==", + "AAAAAgAAAAAAAAAAAAAAClByb2plY3RLZXkAAAAAAAQAAAABAAAAAAAAAANLZXkAAAAAAQAAAA4AAAABAAAAAAAAAAhMYXN0SGFzaAAAAAEAAAAOAAAAAQAAAAAAAAADRGFvAAAAAAIAAAAOAAAABAAAAAEAAAAAAAAAEURhb1RvdGFsUHJvcG9zYWxzAAAAAAAAAQAAAA4=", + "AAAAAQAAAAAAAAAAAAAABkNvbmZpZwAAAAAAAgAAAAAAAAAEaGFzaAAAABAAAAAAAAAAA3VybAAAAAAQ", + "AAAAAQAAAAAAAAAAAAAAB1Byb2plY3QAAAAAAwAAAAAAAAAGY29uZmlnAAAAAAfQAAAABkNvbmZpZwAAAAAAAAAAAAttYWludGFpbmVycwAAAAPqAAAAEwAAAAAAAAAEbmFtZQAAABA=", ]), options, ); } public readonly fromJSON = { + create_proposal: this.txFromJSON, + vote: this.txFromJSON, + get_dao: this.txFromJSON, + get_proposal: this.txFromJSON, init: this.txFromJSON, - version: this.txFromJSON, upgrade: this.txFromJSON, + version: this.txFromJSON, register: this.txFromJSON, update_config: this.txFromJSON, commit: this.txFromJSON,