/// This module demonstrates a basic messageboard using ACL to control the access. /// Admins can /// (1) create their messageboard /// (2) add a partipant to its access control list (ACL) /// (3) remove a participant from its ACL /// participant can /// (1) register for the board /// (2) send a new message /// /// The module also emits events for subscribers /// (1) message change event, this event contains the board, message and message author module MessageBoard::ACLBasedMB{ use std::acl::Self; use aptos_std::event::{Self, EventHandle}; use std::signer; use std::vector; // Error map const EACCOUNT_NOT_IN_ACL: u64 = 1; const ECANNOT_REMOVE_ADMIN_FROM_ACL: u64 = 2; struct ACLBasedMB has key { participants: acl::ACL, pinned_post: vector } struct MessageChangeEventHandle has key { change_events: EventHandle } /// emit an event from participant account showing the board and the new message struct MessageChangeEvent has store, drop { message: vector, participant: address } /// init message board public entry fun message_board_init(account: &signer) { let board = ACLBasedMB{ participants: acl::empty(), pinned_post: vector::empty() }; acl::add(&mut board.participants, signer::address_of(account)); move_to(account, board); move_to(account, MessageChangeEventHandle{ change_events: event::new_event_handle(account) }) } public fun view_message(board_addr: address): vector acquires ACLBasedMB { let post = borrow_global(board_addr).pinned_post; copy post } /// board owner control adding new participants public entry fun add_participant(account: &signer, participant: address) acquires ACLBasedMB { let board = borrow_global_mut(signer::address_of(account)); acl::add(&mut board.participants, participant); } /// remove a participant from the ACL public entry fun remove_participant(account: signer, participant: address) acquires ACLBasedMB { let board = borrow_global_mut(signer::address_of(&account)); assert!(signer::address_of(&account) != participant, ECANNOT_REMOVE_ADMIN_FROM_ACL); acl::remove(&mut board.participants, participant); } /// an account publish the message to update the notice public entry fun send_pinned_message( account: &signer, board_addr: address, message: vector ) acquires ACLBasedMB, MessageChangeEventHandle { let board = borrow_global(board_addr); assert!(acl::contains(&board.participants, signer::address_of(account)), EACCOUNT_NOT_IN_ACL); let board = borrow_global_mut(board_addr); board.pinned_post = message; let send_acct = signer::address_of(account); let event_handle = borrow_global_mut(board_addr); event::emit_event( &mut event_handle.change_events, MessageChangeEvent{ message, participant: send_acct } ); } /// an account can send events containing message public entry fun send_message_to( account: signer, board_addr: address, message: vector ) acquires MessageChangeEventHandle { let event_handle = borrow_global_mut(board_addr); event::emit_event( &mut event_handle.change_events, MessageChangeEvent{ message, participant: signer::address_of(&account) } ); } } #[test_only] module MessageBoard::MessageBoardTests { use std::unit_test; use std::vector; use std::signer; use MessageBoard::ACLBasedMB; const HELLO_WORLD: vector = vector[150, 145, 154, 154, 157, 040, 167, 157, 162, 154, 144]; const BOB_IS_HERE: vector = vector[142, 157, 142, 040, 151, 163, 040, 150, 145, 162, 145]; #[test] public entry fun test_init_messageboard() { let (alice, _) = create_two_signers(); ACLBasedMB::message_board_init(&alice); ACLBasedMB::send_pinned_message(&alice, signer::address_of(&alice), HELLO_WORLD); } #[test] public entry fun test_send_pinned_message() { let (alice, bob) = create_two_signers(); ACLBasedMB::message_board_init(&alice); ACLBasedMB::add_participant(&alice, signer::address_of(&bob)); ACLBasedMB::send_pinned_message(&bob, signer::address_of(&alice), BOB_IS_HERE); let message = ACLBasedMB::view_message(signer::address_of(&alice)); assert!( message == BOB_IS_HERE, 1); let message = ACLBasedMB::view_message(signer::address_of(&alice)); assert!( message == BOB_IS_HERE, 1); } #[test] public entry fun test_send_message_v_cap() { let (alice, bob) = create_two_signers(); ACLBasedMB::message_board_init(&alice); ACLBasedMB::send_message_to(bob, signer::address_of(&alice), BOB_IS_HERE); } #[test] public entry fun read_message_multiple_times() { let (alice, bob) = create_two_signers(); ACLBasedMB::message_board_init(&alice); ACLBasedMB::add_participant(&alice, signer::address_of(&bob)); ACLBasedMB::send_pinned_message(&bob, signer::address_of(&alice), BOB_IS_HERE); let message = ACLBasedMB::view_message(signer::address_of(&alice)); assert!( message == BOB_IS_HERE, 1); let message = ACLBasedMB::view_message(signer::address_of(&alice)); assert!( message == BOB_IS_HERE, 1); } #[test] #[expected_failure(abort_code = 1)] public entry fun test_add_new_participant() { let (alice, bob) = create_two_signers(); ACLBasedMB::message_board_init(&alice); ACLBasedMB::send_pinned_message(&bob, signer::address_of(&alice), BOB_IS_HERE); } #[test_only] fun create_two_signers(): (signer, signer) { let signers = &mut unit_test::create_signers_for_testing(2); (vector::pop_back(signers), vector::pop_back(signers)) } }