From 25e01e35015d56b85551959b50fc9ac3bc624fba Mon Sep 17 00:00:00 2001 From: Peter Jung Date: Mon, 27 Jan 2025 16:47:57 +0100 Subject: [PATCH] Allow popping of messages at specified indices (#23) --- README.md | 2 +- src/NFT/AgentCommunication.sol | 7 ++- src/NFT/DoubleEndedStructQueue.sol | 22 +++++++++ test/AgentCommunication.t.sol | 73 ++++++++++++++++++++---------- 4 files changed, 78 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 7e4b5e0..fcf7c6a 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Repository holding the contracts made by Gnosis Labs team. | OmenThumbnailMapping | Manages IPFS hashes for market thumbnails on Omen 2.0 | [0xe0cf08311F03850497B0ed6A2cf067f1750C3eFc](https://gnosisscan.io/address/0xe0cf08311f03850497b0ed6a2cf067f1750c3efc#code) | [omen-thumbnailmapping](https://thegraph.com/studio/subgraph/omen-thumbnailmapping/) | | OmenAgentResultMapping | Maps prediction results to markets on Omen 2.0 | [0xbe1F6944496923683ca849fc0cC93fD10523cB83](https://gnosisscan.io/address/0x260E1077dEA98e738324A6cEfB0EE9A272eD471a#code) | [omen-agentresultmapping](https://thegraph.com/studio/subgraph/omen-agentresultmapping/) | | Agent NFT | Agent NFTs that control mechs for NFT game | [0x0D7C0Bd4169D090038c6F41CFd066958fe7619D0](https://gnosisscan.io/address/0x0D7C0Bd4169D090038c6F41CFd066958fe7619D0#code) | | -| Agent communication contract | Simple contract storing message queue for each agent | [0xc566Cb829Ed7aC097D17a38011A40Ad2DC25Dd82](https://gnosisscan.io/address/0xc566Cb829Ed7aC097D17a38011A40Ad2DC25Dd82#code) | | +| Agent communication contract | Simple contract storing message queue for each agent | [0xe9dd78FF297DbaAbe5D0E45aE554a4B561935DE9](https://gnosisscan.io/address/0xe9dd78FF297DbaAbe5D0E45aE554a4B561935DE9#code) | | | Simple Treasury contract | Contract for storing the NFT agent game treasury | [0x624ad0db52e6b18afb4d36b8e79d0c2a74f3fc8a](https://gnosisscan.io/address/0x624ad0db52e6b18afb4d36b8e79d0c2a74f3fc8a#code) | | ## Set up contracts development diff --git a/src/NFT/AgentCommunication.sol b/src/NFT/AgentCommunication.sol index 3b35b61..3465f41 100644 --- a/src/NFT/AgentCommunication.sol +++ b/src/NFT/AgentCommunication.sol @@ -70,11 +70,14 @@ contract AgentCommunication is Ownable { return DoubleEndedStructQueue.at(queues[agentAddress], idx); } - function popNextMessage(address agentAddress) public returns (DoubleEndedStructQueue.MessageContainer memory) { + function popMessageAtIndex(address agentAddress, uint256 idx) + public + returns (DoubleEndedStructQueue.MessageContainer memory) + { if (msg.sender != agentAddress) { revert MessageNotSentByAgent(); } - DoubleEndedStructQueue.MessageContainer memory message = DoubleEndedStructQueue.popFront(queues[agentAddress]); + DoubleEndedStructQueue.MessageContainer memory message = DoubleEndedStructQueue.popAt(queues[agentAddress], idx); emit LogMessage(message.sender, agentAddress, message.message, message.value); return message; } diff --git a/src/NFT/DoubleEndedStructQueue.sol b/src/NFT/DoubleEndedStructQueue.sol index a2cb97e..18ee641 100644 --- a/src/NFT/DoubleEndedStructQueue.sol +++ b/src/NFT/DoubleEndedStructQueue.sol @@ -78,6 +78,28 @@ library DoubleEndedStructQueue { } } + function popAt(Bytes32Deque storage deque, uint256 index) internal returns (MessageContainer memory value) { + if (index >= length(deque)) Panic.panic(Panic.ARRAY_OUT_OF_BOUNDS); + unchecked { + if (index == 0) { + return popFront(deque); + } else if (index == length(deque) - 1) { + return popBack(deque); + } else { + uint128 actualIndex = deque._begin + uint128(index); + value = deque._data[actualIndex]; + delete deque._data[actualIndex]; + + // Shift elements to fill the gap + for (uint128 i = actualIndex; i < deque._end - 1; i++) { + deque._data[i] = deque._data[i + 1]; + } + delete deque._data[deque._end - 1]; + deque._end--; + } + } + } + function clear(Bytes32Deque storage deque) internal { deque._begin = 0; deque._end = 0; diff --git a/test/AgentCommunication.t.sol b/test/AgentCommunication.t.sol index e64a251..fdcba3e 100644 --- a/test/AgentCommunication.t.sol +++ b/test/AgentCommunication.t.sol @@ -13,11 +13,15 @@ contract AgentCommunicationTest is Test { address payable treasury = payable(address(0x789)); uint256 pctToTreasuryInBasisPoints = 7000; - function buildMessage() public view returns (DoubleEndedStructQueue.MessageContainer memory) { + function buildMessage(bytes memory customMessage) + public + view + returns (DoubleEndedStructQueue.MessageContainer memory) + { return DoubleEndedStructQueue.MessageContainer({ sender: agent, recipient: address(0x789), - message: "Hello, Agent!", + message: customMessage, value: 1000000000000000000 }); } @@ -56,7 +60,7 @@ contract AgentCommunicationTest is Test { } function testSendMessage() public { - DoubleEndedStructQueue.MessageContainer memory message = buildMessage(); + DoubleEndedStructQueue.MessageContainer memory message = buildMessage("Hello!"); vm.deal(owner, 1 ether); // Record initial balances @@ -82,7 +86,7 @@ contract AgentCommunicationTest is Test { } function testSendMessageInsufficientValue() public { - DoubleEndedStructQueue.MessageContainer memory message = buildMessage(); + DoubleEndedStructQueue.MessageContainer memory message = buildMessage("Hello!"); vm.deal(agent, 1 ether); vm.startPrank(agent); vm.expectRevert("Insufficient message value"); @@ -91,7 +95,7 @@ contract AgentCommunicationTest is Test { } function testNewMessageSentEvent() public { - DoubleEndedStructQueue.MessageContainer memory message = buildMessage(); + DoubleEndedStructQueue.MessageContainer memory message = buildMessage("Hello!"); vm.deal(agent, 1 ether); vm.startPrank(agent); @@ -104,38 +108,61 @@ contract AgentCommunicationTest is Test { vm.stopPrank(); } - function testPopNextMessage() public { - // Create a message container - DoubleEndedStructQueue.MessageContainer memory message = buildMessage(); + function testPopMessage() public { + // Create a message containers + DoubleEndedStructQueue.MessageContainer memory message_1 = buildMessage("Hello 1!"); + DoubleEndedStructQueue.MessageContainer memory message_2 = buildMessage("Hello 2!"); + DoubleEndedStructQueue.MessageContainer memory message_3 = buildMessage("Hello 3!"); + DoubleEndedStructQueue.MessageContainer memory message_4 = buildMessage("Hello 4!"); + DoubleEndedStructQueue.MessageContainer memory message_5 = buildMessage("Hello 5!"); // Fund the agent and start the prank - vm.deal(agent, 1 ether); + vm.deal(agent, 5 ether); vm.startPrank(agent); - // Send the message - agentComm.sendMessage{value: message.value}(message.recipient, message.message); + // Send the messages + agentComm.sendMessage{value: message_1.value}(message_1.recipient, message_1.message); + agentComm.sendMessage{value: message_2.value}(message_2.recipient, message_2.message); + agentComm.sendMessage{value: message_3.value}(message_3.recipient, message_3.message); + agentComm.sendMessage{value: message_4.value}(message_4.recipient, message_4.message); + agentComm.sendMessage{value: message_5.value}(message_5.recipient, message_5.message); vm.stopPrank(); // Start the prank again for popping the message - vm.startPrank(message.recipient); + vm.startPrank(message_1.recipient); // Expect the LogMessage event to be emitted when popping the message vm.expectEmit(true, true, true, true); - emit AgentCommunication.LogMessage(message.sender, message.recipient, message.message, message.value); + emit AgentCommunication.LogMessage(message_1.sender, message_1.recipient, message_1.message, message_1.value); // Pop the next message - DoubleEndedStructQueue.MessageContainer memory poppedMessage = agentComm.popNextMessage(message.recipient); + DoubleEndedStructQueue.MessageContainer memory poppedMessage_1 = + agentComm.popMessageAtIndex(message_1.recipient, 0); + vm.stopPrank(); + + // Assert that the popped message matches the original message + assertEq(poppedMessage_1.sender, message_1.sender); + assertEq(poppedMessage_1.recipient, message_1.recipient); + assertEq(poppedMessage_1.message, message_1.message); + assertEq(poppedMessage_1.value, message_1.value); + + // Start the prank again for popping another message + vm.startPrank(message_1.recipient); + + // Pop the message at specified index + DoubleEndedStructQueue.MessageContainer memory poppedMessage_2 = + agentComm.popMessageAtIndex(message_1.recipient, 2); vm.stopPrank(); // Assert that the popped message matches the original message - assertEq(poppedMessage.sender, message.sender); - assertEq(poppedMessage.recipient, message.recipient); - assertEq(poppedMessage.message, message.message); - assertEq(poppedMessage.value, message.value); + assertEq(poppedMessage_2.sender, message_4.sender); + assertEq(poppedMessage_2.recipient, message_4.recipient); + assertEq(poppedMessage_2.message, message_4.message); + assertEq(poppedMessage_2.value, message_4.value); } - function testPopNextMessageNotByAgent() public { - DoubleEndedStructQueue.MessageContainer memory message = buildMessage(); + function testPopMessageNotByAgent() public { + DoubleEndedStructQueue.MessageContainer memory message = buildMessage("Hello!"); vm.deal(agent, 1 ether); vm.startPrank(agent); agentComm.sendMessage{value: 10000000000000}(agent, message.message); @@ -144,15 +171,15 @@ contract AgentCommunicationTest is Test { address notAgent = address(0x789); vm.startPrank(notAgent); vm.expectRevert(AgentCommunication.MessageNotSentByAgent.selector); - agentComm.popNextMessage(agent); + agentComm.popMessageAtIndex(agent, 0); vm.stopPrank(); } function testCountMessages() public { // Initialize a message - DoubleEndedStructQueue.MessageContainer memory message1 = buildMessage(); + DoubleEndedStructQueue.MessageContainer memory message1 = buildMessage("Hello!"); - DoubleEndedStructQueue.MessageContainer memory message2 = buildMessage(); + DoubleEndedStructQueue.MessageContainer memory message2 = buildMessage("Hello!"); // Fund the agent and start the prank vm.deal(agent, 1 ether);