Skip to content

Commit

Permalink
Governance Chat: Voter weight (#2778)
Browse files Browse the repository at this point in the history
* chore: update governance chat program version to 0.2.0

* feat: support voter weight for chat

* chore: move ensure_voter_weight_addin_is_built to test sdk

* chore: add program version to logs

* fix: workaround for logs

* chore: add test for chat message using voter weight addin

* chore: remove logger overrides

Co-authored-by: Jon Cinque
  • Loading branch information
SebastianBor committed Jan 24, 2022
1 parent 44b338f commit 2ae2f5f
Show file tree
Hide file tree
Showing 14 changed files with 246 additions and 74 deletions.
5 changes: 3 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions governance/chat/program/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "spl-governance-chat"
version = "0.1.0"
version = "0.2.0"
description = "Solana Program Library Governance Chat Program"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana-program-library"
Expand All @@ -21,7 +21,7 @@ serde = "1.0.127"
serde_derive = "1.0.103"
solana-program = "1.9.5"
spl-token = { version = "3.3", path = "../../../token/program", features = [ "no-entrypoint" ] }
spl-governance= { version = "2.1.3", path ="../../program", features = [ "no-entrypoint" ]}
spl-governance= { version = "2.1.5", path ="../../program", features = [ "no-entrypoint" ]}
spl-governance-tools= { version = "0.1.0", path ="../../tools"}
thiserror = "1.0"

Expand All @@ -33,6 +33,7 @@ proptest = "1.0"
solana-program-test = "1.9.5"
solana-sdk = "1.9.5"
spl-governance-test-sdk = { version = "0.1.0", path ="../../test-sdk"}
spl-governance-voter-weight-addin = { version = "0.1.0", path ="../../voter-weight-addin/program"}


[lib]
Expand Down
43 changes: 32 additions & 11 deletions governance/chat/program/src/instruction.rs
Expand Up @@ -6,6 +6,7 @@ use solana_program::{
pubkey::Pubkey,
system_program,
};
use spl_governance::instruction::with_voter_weight_accounts;

use crate::state::MessageBody;

Expand All @@ -16,18 +17,25 @@ pub enum GovernanceChatInstruction {
/// Posts a message with a comment for a Proposal
///
/// 0. `[]` Governance program id
/// 1. `[]` Governance account the Proposal is for
/// 2. `[]` Proposal account
/// 3. `[]` TokenOwnerRecord account for the message author
/// 4. `[signer]` Governance Authority (TokenOwner or Governance Delegate)
/// 5. `[writable, signer]` ChatMessage account
/// 6. `[signer]` Payer
/// 7. `[]` System program
/// 8. `[]` ReplyTo Message account (optional)
/// 1. `[]` Realm account of the Proposal
/// 2. `[]` Governance account the Proposal is for
/// 3. `[]` Proposal account
/// 4. `[]` TokenOwnerRecord account for the message author
/// 5. `[signer]` Governance Authority (TokenOwner or Governance Delegate)
/// 6. `[writable, signer]` ChatMessage account
/// 7. `[signer]` Payer
/// 8. `[]` System program
/// 9. `[]` ReplyTo Message account (optional)
/// 10. `[]` Optional Voter Weight Record
PostMessage {
#[allow(dead_code)]
/// Message body (text or reaction)
body: MessageBody,

#[allow(dead_code)]
/// Indicates whether the message is a reply to another message
/// If yes then ReplyTo Message account has to be provided
is_reply: bool,
},
}

Expand All @@ -37,18 +45,21 @@ pub fn post_message(
program_id: &Pubkey,
// Accounts
governance_program_id: &Pubkey,
realm: &Pubkey,
governance: &Pubkey,
proposal: &Pubkey,
token_owner_record: &Pubkey,
governance_authority: &Pubkey,
reply_to: Option<Pubkey>,
chat_message: &Pubkey,
payer: &Pubkey,
voter_weight_record: Option<Pubkey>,
// Args
body: MessageBody,
) -> Instruction {
let mut accounts = vec![
AccountMeta::new_readonly(*governance_program_id, false),
AccountMeta::new_readonly(*realm, false),
AccountMeta::new_readonly(*governance, false),
AccountMeta::new_readonly(*proposal, false),
AccountMeta::new_readonly(*token_owner_record, false),
Expand All @@ -58,11 +69,21 @@ pub fn post_message(
AccountMeta::new_readonly(system_program::id(), false),
];

if let Some(reply_to) = reply_to {
let is_reply = if let Some(reply_to) = reply_to {
accounts.push(AccountMeta::new_readonly(reply_to, false));
}
true
} else {
false
};

with_voter_weight_accounts(
governance_program_id,
&mut accounts,
realm,
voter_weight_record,
);

let instruction = GovernanceChatInstruction::PostMessage { body };
let instruction = GovernanceChatInstruction::PostMessage { body, is_reply };

Instruction {
program_id: *program_id,
Expand Down
52 changes: 33 additions & 19 deletions governance/chat/program/src/processor.rs
Expand Up @@ -17,8 +17,8 @@ use solana_program::{
sysvar::Sysvar,
};
use spl_governance::state::{
governance::get_governance_data, proposal::get_proposal_data_for_governance,
token_owner_record::get_token_owner_record_data_for_realm,
governance::get_governance_data_for_realm, proposal::get_proposal_data_for_governance,
realm::get_realm_data, token_owner_record::get_token_owner_record_data_for_realm,
};
use spl_governance_tools::account::create_and_serialize_account;

Expand All @@ -28,13 +28,15 @@ pub fn process_instruction(
accounts: &[AccountInfo],
input: &[u8],
) -> ProgramResult {
msg!("VERSION:{:?}", env!("CARGO_PKG_VERSION"));

let instruction = GovernanceChatInstruction::try_from_slice(input)
.map_err(|_| ProgramError::InvalidInstructionData)?;

match instruction {
GovernanceChatInstruction::PostMessage { body } => {
GovernanceChatInstruction::PostMessage { body, is_reply } => {
msg!("GOVERNANCE-CHAT-INSTRUCTION: PostMessage");
process_post_message(program_id, accounts, body)
process_post_message(program_id, accounts, body, is_reply)
}
}
}
Expand All @@ -44,33 +46,38 @@ pub fn process_post_message(
program_id: &Pubkey,
accounts: &[AccountInfo],
body: MessageBody,
is_reply: bool,
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();

let governance_program_info = next_account_info(account_info_iter)?; // 0
let governance_info = next_account_info(account_info_iter)?; // 1
let proposal_info = next_account_info(account_info_iter)?; // 2
let token_owner_record_info = next_account_info(account_info_iter)?; // 3
let governance_authority_info = next_account_info(account_info_iter)?; // 4

let chat_message_info = next_account_info(account_info_iter)?; // 5
let realm_info = next_account_info(account_info_iter)?; // 1
let governance_info = next_account_info(account_info_iter)?; // 2
let proposal_info = next_account_info(account_info_iter)?; // 3
let token_owner_record_info = next_account_info(account_info_iter)?; // 4
let governance_authority_info = next_account_info(account_info_iter)?; // 5

let payer_info = next_account_info(account_info_iter)?; // 6
let system_info = next_account_info(account_info_iter)?; // 7
let chat_message_info = next_account_info(account_info_iter)?; // 6

let reply_to_info = next_account_info(account_info_iter); // 8
let payer_info = next_account_info(account_info_iter)?; // 7
let system_info = next_account_info(account_info_iter)?; // 8

let reply_to_address = if let Ok(reply_to_info) = reply_to_info {
let reply_to_address = if is_reply {
let reply_to_info = next_account_info(account_info_iter)?; // 9
assert_is_valid_chat_message(program_id, reply_to_info)?;
Some(*reply_to_info.key)
} else {
None
};

let governance_data = get_governance_data(governance_program_info.key, governance_info)?;
let governance_program_id = governance_program_info.key;
let realm_data = get_realm_data(governance_program_id, realm_info)?;

let governance_data =
get_governance_data_for_realm(governance_program_id, governance_info, realm_info.key)?;

let token_owner_record_data = get_token_owner_record_data_for_realm(
governance_program_info.key,
governance_program_id,
token_owner_record_info,
&governance_data.realm,
)?;
Expand All @@ -79,15 +86,22 @@ pub fn process_post_message(

// deserialize proposal to assert it belongs to the given governance and hence belongs to the same realm as the token owner
let _proposal_data = get_proposal_data_for_governance(
governance_program_info.key,
governance_program_id,
proposal_info,
governance_info.key,
)?;

// The owner needs to have at least 1 governing token to comment on proposals
let voter_weight = token_owner_record_data.resolve_voter_weight(
governance_program_id,
account_info_iter, // 10
realm_info.key,
&realm_data,
)?;

// The owner needs to have at least voter weight of 1 to comment on proposals
// Note: It can be either community or council token and is irrelevant to the proposal's governing token
// Note: 1 is currently hardcoded but if different level is required then it should be added to realm config
if token_owner_record_data.governing_token_deposit_amount < 1 {
if voter_weight < 1 {
return Err(GovernanceChatError::NotEnoughTokensToCommentProposal.into());
}

Expand Down
21 changes: 21 additions & 0 deletions governance/chat/program/tests/process_post_message.rs
Expand Up @@ -128,3 +128,24 @@ async fn test_post_message_with_not_enough_tokens_error() {
GovernanceChatError::NotEnoughTokensToCommentProposal.into()
);
}

#[tokio::test]
async fn test_post_message_with_voter_weight_addin() {
// Arrange
let mut governance_chat_test = GovernanceChatProgramTest::start_with_voter_weight_addin().await;

let proposal_cookie = governance_chat_test.with_proposal().await;

// Act
let chat_message_cookie = governance_chat_test
.with_chat_message(&proposal_cookie, None)
.await
.unwrap();

// Assert
let chat_message_data = governance_chat_test
.get_message_account(&chat_message_cookie.address)
.await;

assert_eq!(chat_message_data, chat_message_cookie.account);
}
2 changes: 2 additions & 0 deletions governance/chat/program/tests/program_test/cookies.rs
Expand Up @@ -18,6 +18,8 @@ pub struct ProposalCookie {

pub governing_token_mint: Pubkey,
pub governing_token_mint_authority: Keypair,

pub voter_weight_record: Option<Pubkey>,
}

#[derive(Debug)]
Expand Down

0 comments on commit 2ae2f5f

Please sign in to comment.