-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* first version * Added struct queue * Small changes
- Loading branch information
1 parent
dd748a5
commit 031881e
Showing
3 changed files
with
317 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
|
||
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); | ||
} | ||
} |