Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add vote execution #121

Merged
merged 2 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions contracts/versioning/src/contract_dao.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,55 @@ impl DaoTrait for Tansu {
}
}

fn execute(
env: Env,
maintainer: Address,
project_key: Bytes,
proposal_id: u32,
) -> types::ProposalStatus {
maintainer.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 = <Tansu as DaoTrait>::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),
};

let curr_timestamp = env.ledger().timestamp();

// only allow to execute once
if proposal.status != types::ProposalStatus::Active {
panic_with_error!(&env, &errors::ContractErrors::AlreadyExecuted);
} else if curr_timestamp < proposal.voting_ends_at {
panic_with_error!(&env, &errors::ContractErrors::ProposalVotingTime);
} else {
// count votes
let voted_approve = proposal.voters_approve.len();
let voted_reject = proposal.voters_reject.len();
let voted_abstain = proposal.voters_abstain.len();

// accept or reject if we have a majority
if voted_approve > (voted_abstain + voted_reject) {
proposal.status = types::ProposalStatus::Approved;
} else if voted_reject > (voted_abstain + voted_approve) {
proposal.status = types::ProposalStatus::Rejected;
} else {
proposal.status = types::ProposalStatus::Cancelled
}

dao_page.proposals.set(sub_id, proposal.clone());

env.storage()
.persistent()
.set(&types::ProjectKey::Dao(project_key_, page), &dao_page);

proposal.status
}
}

/// Get one page of proposal of the DAO.
/// A page has 0 to MAX_PROPOSALS_PER_PAGE proposals.
/// # Arguments
Expand Down
1 change: 1 addition & 0 deletions contracts/versioning/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ pub enum ContractErrors {
NoProposalorPageFound = 8,
AlreadyVoted = 9,
ProposalVotingTime = 10,
AlreadyExecuted = 11,
}
7 changes: 7 additions & 0 deletions contracts/versioning/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ pub trait DaoTrait {

fn vote(env: Env, voter: Address, project_key: Bytes, proposal_id: u32, vote: types::Vote);

fn execute(
env: Env,
maintainer: Address,
project_key: Bytes,
proposal_id: u32,
) -> types::ProposalStatus;

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;
Expand Down
32 changes: 31 additions & 1 deletion contracts/versioning/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
use super::{domain_contract, Tansu, TansuClient};
use crate::contract_versioning::{domain_node, domain_register};
use crate::errors::ContractErrors;
use crate::types::{Dao, Vote};
use crate::types::{Dao, ProposalStatus, Vote};
use soroban_sdk::testutils::Address as _;
use soroban_sdk::testutils::Ledger as _;
use soroban_sdk::{
symbol_short, testutils::Events, token, vec, Address, Bytes, Env, IntoVal, String, Vec,
};
Expand Down Expand Up @@ -228,9 +229,38 @@ fn test() {
assert_eq!(error, ContractErrors::AlreadyVoted.into());

let proposal = contract.get_proposal(&id, &proposal_id);
assert_eq!(proposal.status, ProposalStatus::Active);
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()]);

// cast another vote and approve
env.ledger().set_timestamp(1234567890);
let kuiil = Address::generate(&env);
let voting_ends_at = 1234567890 + 3600 * 24 * 2;
let proposal_id_2 = contract.create_proposal(&grogu, &id, &title, &ipfs, &voting_ends_at);
contract.vote(&mando, &id, &proposal_id_2, &Vote::Approve);
contract.vote(&kuiil, &id, &proposal_id_2, &Vote::Approve);

// too early to execute
let error = contract
.try_execute(&mando, &id, &proposal_id_2)
.unwrap_err()
.unwrap();
assert_eq!(error, ContractErrors::ProposalVotingTime.into());

env.ledger().set_timestamp(voting_ends_at + 1);

let vote_result = contract.execute(&mando, &id, &proposal_id_2);
assert_eq!(vote_result, ProposalStatus::Approved);
let proposal_2 = contract.get_proposal(&id, &proposal_id_2);
assert_eq!(
proposal_2.voters_approve,
vec![&env, mando.clone(), kuiil.clone()]
);
assert_eq!(proposal_2.voters_reject, Vec::new(&env));
assert_eq!(proposal_2.voters_abstain, vec![&env, grogu.clone()]);
assert_eq!(proposal_2.status, ProposalStatus::Approved);
}

#[test]
Expand Down
4 changes: 2 additions & 2 deletions dapp/packages/soroban_versioning/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ This library was automatically generated by Soroban CLI using a command similar
soroban contract bindings ts \
--rpc-url https://soroban-testnet.stellar.org \
--network-passphrase "Test SDF Network ; September 2015" \
--contract-id CCHZAAUJJZOCFZVTQWGZU4PXR7SFB5PL3XBSHUBNQPXJ3KWE4VFHOBE5 \
--contract-id CB6EA5V4T5Z5XXOKHRU5MNYTZ2QESR4STUW6EBLA4HXYZ2BPVMCHRHUR \
--output-dir ./path/to/soroban_versioning
```

Expand All @@ -30,7 +30,7 @@ However, we've actually encountered [frustration](https://github.com/stellar/sor

```json
"scripts": {
"postinstall": "soroban contract bindings ts --rpc-url https://soroban-testnet.stellar.org --network-passphrase \"Test SDF Network ; September 2015\" --id CCHZAAUJJZOCFZVTQWGZU4PXR7SFB5PL3XBSHUBNQPXJ3KWE4VFHOBE5 --name soroban_versioning"
"postinstall": "soroban contract bindings ts --rpc-url https://soroban-testnet.stellar.org --network-passphrase \"Test SDF Network ; September 2015\" --id CB6EA5V4T5Z5XXOKHRU5MNYTZ2QESR4STUW6EBLA4HXYZ2BPVMCHRHUR --name soroban_versioning"
}
```

Expand Down
34 changes: 33 additions & 1 deletion dapp/packages/soroban_versioning/dist/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export * as rpc from "@stellar/stellar-sdk/rpc";
export declare const networks: {
readonly testnet: {
readonly networkPassphrase: "Test SDF Network ; September 2015";
readonly contractId: "CCHZAAUJJZOCFZVTQWGZU4PXR7SFB5PL3XBSHUBNQPXJ3KWE4VFHOBE5";
readonly contractId: "CB6EA5V4T5Z5XXOKHRU5MNYTZ2QESR4STUW6EBLA4HXYZ2BPVMCHRHUR";
};
};
export declare const Errors: {
Expand Down Expand Up @@ -48,6 +48,9 @@ export declare const Errors: {
10: {
message: string;
};
11: {
message: string;
};
};
export type DataKey = {
tag: "Admin";
Expand Down Expand Up @@ -205,6 +208,34 @@ export interface Client {
simulate?: boolean;
},
) => Promise<AssembledTransaction<null>>;
/**
* Construct and simulate a execute 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.
*/
execute: (
{
maintainer,
project_key,
proposal_id,
}: {
maintainer: string;
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<AssembledTransaction<ProposalStatus>>;
/**
* 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.
Expand Down Expand Up @@ -466,6 +497,7 @@ export declare class Client extends ContractClient {
readonly fromJSON: {
create_proposal: (json: string) => AssembledTransaction<number>;
vote: (json: string) => AssembledTransaction<null>;
execute: (json: string) => AssembledTransaction<ProposalStatus>;
get_dao: (json: string) => AssembledTransaction<Dao>;
get_proposal: (json: string) => AssembledTransaction<Proposal>;
upgrade: (json: string) => AssembledTransaction<null>;
Expand Down
7 changes: 5 additions & 2 deletions dapp/packages/soroban_versioning/dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ if (typeof window !== "undefined") {
export const networks = {
testnet: {
networkPassphrase: "Test SDF Network ; September 2015",
contractId: "CCHZAAUJJZOCFZVTQWGZU4PXR7SFB5PL3XBSHUBNQPXJ3KWE4VFHOBE5",
contractId: "CB6EA5V4T5Z5XXOKHRU5MNYTZ2QESR4STUW6EBLA4HXYZ2BPVMCHRHUR",
},
};
export const Errors = {
Expand All @@ -28,6 +28,7 @@ export const Errors = {
8: { message: "NoProposalorPageFound" },
9: { message: "AlreadyVoted" },
10: { message: "ProposalVotingTime" },
11: { message: "AlreadyExecuted" },
};
export class Client extends ContractClient {
options;
Expand All @@ -36,6 +37,7 @@ export class Client extends ContractClient {
new ContractSpec([
"AAAAAAAAAcFDcmVhdGUgYSBwcm9wb3NhbCBvbiB0aGUgREFPIG9mIHRoZSBwcm9qZWN0LgpQcm9wb3NhbCBpbml0aWF0b3JzIGFyZSBhdXRvbWF0aWNhbGx5IHB1dCBpbiB0aGUgYWJzdGFpbiBncm91cC4KIyBBcmd1bWVudHMKKiBgZW52YCAtIFRoZSBlbnZpcm9ubWVudCBvYmplY3QKKiBgcHJvcG9zZXJgIC0gQWRkcmVzcyBvZiB0aGUgcHJvcG9zYWwgY3JlYXRvcgoqIGBwcm9qZWN0X2tleWAgLSBVbmlxdWUgaWRlbnRpZmllciBmb3IgdGhlIHByb2plY3QKKiBgdGl0bGVgIC0gVGl0bGUgb2YgdGhlIHByb3Bvc2FsCiogYGlwZnNgIC0gSVBGUyBjb250ZW50IGlkZW50aWZpZXIgZGVzY3JpYmluZyB0aGUgcHJvcG9zYWwKKiBgdm90aW5nX2VuZHNfYXRgIC0gVU5JWCB0aW1lc3RhbXAgd2hlbiB2b3RpbmcgZW5kcwojIFJldHVybnMKKiBgdTMyYCAtIFRoZSBJRCBvZiB0aGUgY3JlYXRlZCBwcm9wb3NhbAAAAAAAAA9jcmVhdGVfcHJvcG9zYWwAAAAABQAAAAAAAAAIcHJvcG9zZXIAAAATAAAAAAAAAAtwcm9qZWN0X2tleQAAAAAOAAAAAAAAAAV0aXRsZQAAAAAAABAAAAAAAAAABGlwZnMAAAAQAAAAAAAAAA52b3RpbmdfZW5kc19hdAAAAAAABgAAAAEAAAAE",
"AAAAAAAAAQ5DYXN0IGEgdm90ZSBvbiBhIHByb3Bvc2FsLgpEb3VibGUgdm90ZXMgYXJlIG5vdCBhbGxvd2VkLgojIEFyZ3VtZW50cwoqIGBlbnZgIC0gVGhlIGVudmlyb25tZW50IG9iamVjdAoqIGB2b3RlcmAgLSBBZGRyZXNzIG9mIHRoZSB2b3RlcgoqIGBwcm9qZWN0X2tleWAgLSBVbmlxdWUgaWRlbnRpZmllciBmb3IgdGhlIHByb2plY3QKKiBgcHJvcG9zYWxfaWRgIC0gSUQgb2YgdGhlIHByb3Bvc2FsCiogYHZvdGVgIC0gQXBwcm92ZSwgcmVqZWN0IG9yIGFic3RhaW4gZGVjaXNpb24AAAAAAAR2b3RlAAAABAAAAAAAAAAFdm90ZXIAAAAAAAATAAAAAAAAAAtwcm9qZWN0X2tleQAAAAAOAAAAAAAAAAtwcm9wb3NhbF9pZAAAAAAEAAAAAAAAAAR2b3RlAAAH0AAAAARWb3RlAAAAAA==",
"AAAAAAAAAAAAAAAHZXhlY3V0ZQAAAAADAAAAAAAAAAptYWludGFpbmVyAAAAAAATAAAAAAAAAAtwcm9qZWN0X2tleQAAAAAOAAAAAAAAAAtwcm9wb3NhbF9pZAAAAAAEAAAAAQAAB9AAAAAOUHJvcG9zYWxTdGF0dXMAAA==",
"AAAAAAAAARRHZXQgb25lIHBhZ2Ugb2YgcHJvcG9zYWwgb2YgdGhlIERBTy4KQSBwYWdlIGhhcyAwIHRvIE1BWF9QUk9QT1NBTFNfUEVSX1BBR0UgcHJvcG9zYWxzLgojIEFyZ3VtZW50cwoqIGBlbnZgIC0gVGhlIGVudmlyb25tZW50IG9iamVjdAoqIGBwcm9qZWN0X2tleWAgLSBVbmlxdWUgaWRlbnRpZmllciBmb3IgdGhlIHByb2plY3QKKiBgcGFnZWAgLSBQYWdlIG9mIHByb3Bvc2FscwojIFJldHVybnMKKiBgdHlwZXM6OkRhb2AgLSBUaGUgRGFvIG9iamVjdCAodmVjdG9yIG9mIHByb3Bvc2FscykAAAAHZ2V0X2RhbwAAAAACAAAAAAAAAAtwcm9qZWN0X2tleQAAAAAOAAAAAAAAAARwYWdlAAAABAAAAAEAAAfQAAAAA0RhbwA=",
"AAAAAAAAANdPbmx5IHJldHVybiBhIHNpbmdsZSBwcm9wb3NhbAojIEFyZ3VtZW50cwoqIGBlbnZgIC0gVGhlIGVudmlyb25tZW50IG9iamVjdAoqIGBwcm9qZWN0X2tleWAgLSBVbmlxdWUgaWRlbnRpZmllciBmb3IgdGhlIHByb2plY3QKKiBgcHJvcG9zYWxfaWRgIC0gSUQgb2YgdGhlIHByb3Bvc2FsCiMgUmV0dXJucwoqIGB0eXBlczo6UHJvcG9zYWxgIC0gVGhlIHByb3Bvc2FsIG9iamVjdAAAAAAMZ2V0X3Byb3Bvc2FsAAAAAgAAAAAAAAALcHJvamVjdF9rZXkAAAAADgAAAAAAAAALcHJvcG9zYWxfaWQAAAAABAAAAAEAAAfQAAAACFByb3Bvc2Fs",
"AAAAAAAAAAAAAAANX19jb25zdHJ1Y3RvcgAAAAAAAAEAAAAAAAAABWFkbWluAAAAAAAAEwAAAAA=",
Expand All @@ -46,7 +48,7 @@ export class Client extends ContractClient {
"AAAAAAAAABhTZXQgdGhlIGxhc3QgY29tbWl0IGhhc2gAAAAGY29tbWl0AAAAAAADAAAAAAAAAAptYWludGFpbmVyAAAAAAATAAAAAAAAAAtwcm9qZWN0X2tleQAAAAAOAAAAAAAAAARoYXNoAAAAEAAAAAA=",
"AAAAAAAAABhHZXQgdGhlIGxhc3QgY29tbWl0IGhhc2gAAAAKZ2V0X2NvbW1pdAAAAAAAAQAAAAAAAAALcHJvamVjdF9rZXkAAAAADgAAAAEAAAAQ",
"AAAAAAAAAAAAAAALZ2V0X3Byb2plY3QAAAAAAQAAAAAAAAALcHJvamVjdF9rZXkAAAAADgAAAAEAAAfQAAAAB1Byb2plY3QA",
"AAAABAAAAAAAAAAAAAAADkNvbnRyYWN0RXJyb3JzAAAAAAALAAAAAAAAAA9VbmV4cGVjdGVkRXJyb3IAAAAAAAAAAAAAAAAKSW52YWxpZEtleQAAAAAAAQAAAAAAAAATUHJvamVjdEFscmVhZHlFeGlzdAAAAAACAAAAAAAAABZVbnJlZ2lzdGVyZWRNYWludGFpbmVyAAAAAAADAAAAAAAAAAtOb0hhc2hGb3VuZAAAAAAEAAAAAAAAABJJbnZhbGlkRG9tYWluRXJyb3IAAAAAAAUAAAAAAAAAGE1haW50YWluZXJOb3REb21haW5Pd25lcgAAAAYAAAAAAAAAF1Byb3Bvc2FsSW5wdXRWYWxpZGF0aW9uAAAAAAcAAAAAAAAAFU5vUHJvcG9zYWxvclBhZ2VGb3VuZAAAAAAAAAgAAAAAAAAADEFscmVhZHlWb3RlZAAAAAkAAAAAAAAAElByb3Bvc2FsVm90aW5nVGltZQAAAAAACg==",
"AAAABAAAAAAAAAAAAAAADkNvbnRyYWN0RXJyb3JzAAAAAAAMAAAAAAAAAA9VbmV4cGVjdGVkRXJyb3IAAAAAAAAAAAAAAAAKSW52YWxpZEtleQAAAAAAAQAAAAAAAAATUHJvamVjdEFscmVhZHlFeGlzdAAAAAACAAAAAAAAABZVbnJlZ2lzdGVyZWRNYWludGFpbmVyAAAAAAADAAAAAAAAAAtOb0hhc2hGb3VuZAAAAAAEAAAAAAAAABJJbnZhbGlkRG9tYWluRXJyb3IAAAAAAAUAAAAAAAAAGE1haW50YWluZXJOb3REb21haW5Pd25lcgAAAAYAAAAAAAAAF1Byb3Bvc2FsSW5wdXRWYWxpZGF0aW9uAAAAAAcAAAAAAAAAFU5vUHJvcG9zYWxvclBhZ2VGb3VuZAAAAAAAAAgAAAAAAAAADEFscmVhZHlWb3RlZAAAAAkAAAAAAAAAElByb3Bvc2FsVm90aW5nVGltZQAAAAAACgAAAAAAAAAPQWxyZWFkeUV4ZWN1dGVkAAAAAAs=",
"AAAAAgAAAAAAAAAAAAAAB0RhdGFLZXkAAAAAAQAAAAAAAAAAAAAABUFkbWluAAAA",
"AAAAAgAAAAAAAAAAAAAADlByb3Bvc2FsU3RhdHVzAAAAAAAEAAAAAAAAAAAAAAAGQWN0aXZlAAAAAAAAAAAAAAAAAAhBcHByb3ZlZAAAAAAAAAAAAAAACFJlamVjdGVkAAAAAAAAAAAAAAAJQ2FuY2VsbGVkAAAA",
"AAAAAgAAAAAAAAAAAAAABFZvdGUAAAADAAAAAAAAAAAAAAAHQXBwcm92ZQAAAAAAAAAAAAAAAAZSZWplY3QAAAAAAAAAAAAAAAAAB0Fic3RhaW4A",
Expand All @@ -63,6 +65,7 @@ export class Client extends ContractClient {
fromJSON = {
create_proposal: this.txFromJSON,
vote: this.txFromJSON,
execute: this.txFromJSON,
get_dao: this.txFromJSON,
get_proposal: this.txFromJSON,
upgrade: this.txFromJSON,
Expand Down
Loading
Loading