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

Agent communication contract #15

Merged
merged 3 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
58 changes: 58 additions & 0 deletions src/NFT/AgentCommunication.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import "./DoubleEndedStructQueue.sol";

contract AgentCommunication is Ownable {
error MessageNotSentByAgent();

mapping(address => DoubleEndedStructQueue.Bytes32Deque) public queues;
uint256 public minimumValueForSendingMessageInWei;

event NewMessageSent(address indexed sender, address indexed agentAddress, bytes32 message);
event MessagePopped(address indexed agentAddress, bytes32 message);

constructor() Ownable(msg.sender) {
minimumValueForSendingMessageInWei = 10000000000000; // 0.00001 xDAI
gabrielfior marked this conversation as resolved.
Show resolved Hide resolved
}

modifier mustPayMoreThanMinimum() {
require(msg.value >= minimumValueForSendingMessageInWei, "Insufficient message value");
_;
}

function adjustMinimumValueForSendingMessage(uint256 newValue) public onlyOwner {
minimumValueForSendingMessageInWei = newValue;
}

function countMessages(address agentAddress) public view returns (uint256) {
return DoubleEndedStructQueue.length(queues[agentAddress]);
}

function sendMessage(address agentAddress, DoubleEndedStructQueue.MessageContainer memory message)
public
payable
mustPayMoreThanMinimum
{
DoubleEndedStructQueue.pushBack(queues[agentAddress], message);
emit NewMessageSent(msg.sender, agentAddress, message.message);
}

function getAtIndex(address agentAddress, uint256 idx)
public
view
returns (DoubleEndedStructQueue.MessageContainer memory)
{
return DoubleEndedStructQueue.at(queues[agentAddress], idx);
}

function popNextMessage(address agentAddress) public returns (DoubleEndedStructQueue.MessageContainer memory) {
if (msg.sender != agentAddress) {
revert MessageNotSentByAgent();
}
DoubleEndedStructQueue.MessageContainer memory message = DoubleEndedStructQueue.popFront(queues[agentAddress]);
emit MessagePopped(agentAddress, message.message);
return message;
}
}
94 changes: 94 additions & 0 deletions src/NFT/DoubleEndedStructQueue.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {Panic} from "@openzeppelin/contracts/utils/Panic.sol";

// Based on OpenZeppelin's DoubleEndedQueue, but with a custom struct as data type instead of bytes32.
// See https://docs.openzeppelin.com/contracts/5.x/api/utils#DoubleEndedQueue
library DoubleEndedStructQueue {
struct MessageContainer {
address sender;
address recipient;
bytes32 message;
}

struct Bytes32Deque {
uint128 _begin;
uint128 _end;
mapping(uint128 index => MessageContainer) _data;
}

function pushBack(Bytes32Deque storage deque, MessageContainer memory container) internal {
unchecked {
uint128 backIndex = deque._end;
if (backIndex + 1 == deque._begin) Panic.panic(Panic.RESOURCE_ERROR);
deque._data[backIndex] = container;
deque._end = backIndex + 1;
}
}

function popBack(Bytes32Deque storage deque) internal returns (MessageContainer memory value) {
unchecked {
uint128 backIndex = deque._end;
if (backIndex == deque._begin) Panic.panic(Panic.EMPTY_ARRAY_POP);
--backIndex;
value = deque._data[backIndex];
delete deque._data[backIndex];
deque._end = backIndex;
}
}

function pushFront(Bytes32Deque storage deque, MessageContainer memory value) internal {
unchecked {
uint128 frontIndex = deque._begin - 1;
if (frontIndex == deque._end) Panic.panic(Panic.RESOURCE_ERROR);
deque._data[frontIndex] = value;
deque._begin = frontIndex;
}
}

function popFront(Bytes32Deque storage deque) internal returns (MessageContainer memory value) {
unchecked {
uint128 frontIndex = deque._begin;
if (frontIndex == deque._end) Panic.panic(Panic.EMPTY_ARRAY_POP);
value = deque._data[frontIndex];
delete deque._data[frontIndex];
deque._begin = frontIndex + 1;
}
}

function front(Bytes32Deque storage deque) internal view returns (MessageContainer memory value) {
if (empty(deque)) Panic.panic(Panic.ARRAY_OUT_OF_BOUNDS);
return deque._data[deque._begin];
}

function back(Bytes32Deque storage deque) internal view returns (MessageContainer memory value) {
if (empty(deque)) Panic.panic(Panic.ARRAY_OUT_OF_BOUNDS);
unchecked {
return deque._data[deque._end - 1];
}
}

function at(Bytes32Deque storage deque, uint256 index) internal view returns (MessageContainer memory value) {
if (index >= length(deque)) Panic.panic(Panic.ARRAY_OUT_OF_BOUNDS);
// By construction, length is a uint128, so the check above ensures that index can be safely downcast to uint128
unchecked {
return deque._data[deque._begin + uint128(index)];
}
}

function clear(Bytes32Deque storage deque) internal {
deque._begin = 0;
deque._end = 0;
}

function length(Bytes32Deque storage deque) internal view returns (uint256) {
unchecked {
return uint256(deque._end - deque._begin);
}
}

function empty(Bytes32Deque storage deque) internal view returns (bool) {
return deque._end == deque._begin;
}
}
165 changes: 165 additions & 0 deletions test/AgentCommunication.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import "forge-std/Test.sol";
import {AgentCommunication} from "../src/NFT/AgentCommunication.sol";
import "../src/NFT/DoubleEndedStructQueue.sol";

contract AgentCommunicationTest is Test {
AgentCommunication agentComm;
address owner = address(0x123);
address agent = address(0x456);

function setUp() public {
vm.startPrank(owner);
agentComm = new AgentCommunication();
vm.stopPrank();
}

function testInitialMinimumValue() public {
uint256 expectedValue = 10000000000000; // 0.00001 xDAI
assertEq(agentComm.minimumValueForSendingMessageInWei(), expectedValue);
}

function testAdjustMinimumValue() public {
uint256 newValue = 20000000000000; // 0.00002 xDAI
vm.startPrank(owner);
agentComm.adjustMinimumValueForSendingMessage(newValue);
vm.stopPrank();
assertEq(agentComm.minimumValueForSendingMessageInWei(), newValue);
}

function testOnlyOwnerCanAdjustMinimumValue() public {
uint256 newValue = 20000000000000; // 0.00002 xDAI
address nonOwner = address(0x789);

// Attempt to adjust the minimum value from a non-owner address
vm.startPrank(nonOwner);
vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, address(nonOwner)));
agentComm.adjustMinimumValueForSendingMessage(newValue);
vm.stopPrank();

// Verify that the value has not changed
assertEq(agentComm.minimumValueForSendingMessageInWei(), 10000000000000);
}

function testSendMessage() public {
DoubleEndedStructQueue.MessageContainer memory message = DoubleEndedStructQueue.MessageContainer({
sender: agent, // or any appropriate address
recipient: address(0x789), // or any appropriate address
message: "Hello, Agent!" // Ensure this is a bytes32 type
});
vm.deal(agent, 1 ether);
vm.startPrank(agent);
agentComm.sendMessage{value: 10000000000000}(agent, message);
vm.stopPrank();

DoubleEndedStructQueue.MessageContainer memory storedMessage = agentComm.getAtIndex(agent, 0);
assertEq(storedMessage.message, message.message);
}

function testSendMessageInsufficientValue() public {
DoubleEndedStructQueue.MessageContainer memory message = DoubleEndedStructQueue.MessageContainer({
sender: agent,
recipient: address(0x789), // or any appropriate address
message: bytes32("Hello, Agent!") // Ensure this is a bytes32 type
});
vm.deal(agent, 1 ether);
vm.startPrank(agent);
vm.expectRevert("Insufficient message value");
agentComm.sendMessage{value: 5000}(agent, message);
vm.stopPrank();
}

function testNewMessageSentEvent() public {
address recipient = address(0x789);
DoubleEndedStructQueue.MessageContainer memory message = DoubleEndedStructQueue.MessageContainer({
sender: agent,
recipient: recipient,
message: bytes32("Hello, Agent!") // Ensure this is a bytes32 type
});
vm.deal(agent, 1 ether);
vm.startPrank(agent);

// Expect the NewMessageSent event to be emitted
vm.expectEmit(true, true, false, true);
emit AgentCommunication.NewMessageSent(agent, message.recipient, message.message);

// Send the message
agentComm.sendMessage{value: 0.2 ether}(recipient, message);
vm.stopPrank();
}

function testPopNextMessage() public {
DoubleEndedStructQueue.MessageContainer memory message = DoubleEndedStructQueue.MessageContainer({
sender: agent,
recipient: address(0x789), // or any appropriate address
message: bytes32("Hello, Agent!") // Ensure this is a bytes32 type
});
vm.deal(agent, 1 ether);
vm.startPrank(agent);
agentComm.sendMessage{value: 10000000000000}(agent, message);
vm.stopPrank();

// Expect the MessagePopped event to be emitted
vm.expectEmit(true, true, false, true);
emit AgentCommunication.MessagePopped(agent, message.message);
vm.startPrank(agent);
DoubleEndedStructQueue.MessageContainer memory poppedMessage = agentComm.popNextMessage(agent);
vm.stopPrank();

assertEq(poppedMessage.message, message.message);
uint256 numMessages = agentComm.countMessages(agent);
assertEq(numMessages, 0);
}

// ToDo - reset name
function testPopNextMessageNotByAgent() public {
DoubleEndedStructQueue.MessageContainer memory message = DoubleEndedStructQueue.MessageContainer({
sender: agent,
recipient: address(0x789), // or any appropriate address
message: bytes32("Hello, Agent!") // Ensure this is a bytes32 type
});
vm.deal(agent, 1 ether);
vm.startPrank(agent);
agentComm.sendMessage{value: 10000000000000}(agent, message);
vm.stopPrank();

address notAgent = address(0x789);
vm.startPrank(notAgent);
vm.expectRevert(AgentCommunication.MessageNotSentByAgent.selector);
agentComm.popNextMessage(agent);
vm.stopPrank();
}

function testCountMessages() public {
// Initialize a message
DoubleEndedStructQueue.MessageContainer memory message1 = DoubleEndedStructQueue.MessageContainer({
sender: agent,
recipient: address(0x789),
message: bytes32("Hello, Agent 1!") // Ensure this is a bytes32 type
});

DoubleEndedStructQueue.MessageContainer memory message2 = DoubleEndedStructQueue.MessageContainer({
sender: agent,
recipient: address(0x789),
message: bytes32("Hello, Agent 2!") // Ensure this is a bytes32 type
});

// Fund the agent and start the prank
vm.deal(agent, 1 ether);
vm.startPrank(agent);

// Send two messages
agentComm.sendMessage{value: 10000000000000}(agent, message1);
agentComm.sendMessage{value: 10000000000000}(agent, message2);

// Stop the prank
vm.stopPrank();

// Check the count of messages
uint256 messageCount = agentComm.countMessages(agent);
assertEq(messageCount, 2, "The message count should be 2");
}
}
Loading