diff --git a/src/NFT/AgentCommunication.sol b/src/NFT/AgentCommunication.sol new file mode 100644 index 0000000..200d9e0 --- /dev/null +++ b/src/NFT/AgentCommunication.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT +// Compatible with OpenZeppelin Contracts ^5.0.0 +pragma solidity ^0.8.22; + +import "@openzeppelin/contracts/utils/structs/DoubleEndedQueue.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; + +contract AgentCommunication is Ownable { + error MessageNotSentByAgent(); + + mapping(address => DoubleEndedQueue.Bytes32Deque) public queues; + uint256 public minimumValueForSendingMessageInWei; + + event NewMessageSent(address indexed sender, bytes32 message); + + constructor() Ownable(msg.sender) { + minimumValueForSendingMessageInWei = 10000000000000; // 0.00001 xDAI + } + + modifier mustPayMoreThanMinimum() { + require(msg.value >= minimumValueForSendingMessageInWei, "Insufficient message value"); + _; + } + + function adjustMinimumValueForSendingMessage(uint256 newValue) public onlyOwner { + minimumValueForSendingMessageInWei = newValue; + } + + function sendMessage(address agentAddress, bytes32 message) public payable mustPayMoreThanMinimum { + DoubleEndedQueue.pushBack(queues[agentAddress], message); + emit NewMessageSent(agentAddress, message); + } + + function getAtIndex(address agentAddress, uint256 idx) public view returns (bytes32) { + return DoubleEndedQueue.at(queues[agentAddress], idx); + } + + function popNextMessage(address agentAddress) public returns (bytes32) { + if (msg.sender != agentAddress) { + revert MessageNotSentByAgent(); + } + return DoubleEndedQueue.popFront(queues[agentAddress]); + } +} diff --git a/test/AgentCommunication.t.sol b/test/AgentCommunication.t.sol new file mode 100644 index 0000000..0147bdc --- /dev/null +++ b/test/AgentCommunication.t.sol @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: MIT +// Compatible with OpenZeppelin Contracts ^5.0.0 +pragma solidity ^0.8.22; + +import "@openzeppelin/contracts/utils/structs/DoubleEndedQueue.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import "forge-std/Test.sol"; +import {AgentCommunication} from "../src/NFT/AgentCommunication.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.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 { + bytes32 message = "Hello, Agent!"; + vm.deal(agent, 1 ether); + vm.startPrank(agent); + agentComm.sendMessage{value: 10000000000000}(agent, message); + vm.stopPrank(); + + bytes32 storedMessage = agentComm.getAtIndex(agent, 0); + assertEq(storedMessage, message); + } + + function testSendMessageInsufficientValue() public { + bytes32 message = "Hello, Agent!"; + vm.deal(agent, 1 ether); + vm.startPrank(agent); + vm.expectRevert("Insufficient message value"); + agentComm.sendMessage{value: 5000}(agent, message); + vm.stopPrank(); + } + + function testPopNextMessage() public { + bytes32 message = "Hello, Agent!"; + vm.deal(agent, 1 ether); + vm.startPrank(agent); + agentComm.sendMessage{value: 10000000000000}(agent, message); + vm.stopPrank(); + + vm.startPrank(agent); + bytes32 poppedMessage = agentComm.popNextMessage(agent); + vm.stopPrank(); + + assertEq(poppedMessage, message); + } + + function testPopNextMessageNotByAgent() public { + bytes32 message = "Hello, Agent!"; + 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(); + } +}