-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* base Mailbox implementation * change underlying data struct to enable needed message traversal and reading methods * impl a func to mark a message as read this way freeing a slot for a new message * allow a recipient to request a message without specifying a sender
- Loading branch information
1 parent
8389dc4
commit 5460c6c
Showing
6 changed files
with
464 additions
and
1 deletion.
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,69 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.20; | ||
|
||
bytes32 constant PRE_HEAD_ADDR = keccak256("preHead"); | ||
bytes32 constant POST_TAIL_ADDR = keccak256("postTail"); | ||
|
||
struct Node { | ||
bytes32 val; | ||
bytes32 next; | ||
bytes32 prev; | ||
} | ||
struct LinkedList { | ||
mapping (bytes32 => Node) nodes; | ||
uint256 size; | ||
} | ||
|
||
library LinkedListInterface { | ||
function init(LinkedList storage self) public { | ||
Node storage preHead = self.nodes[PRE_HEAD_ADDR]; | ||
Node storage postTail = self.nodes[POST_TAIL_ADDR]; | ||
if(preHead.next != 0) return; | ||
preHead.next = POST_TAIL_ADDR; | ||
postTail.prev = PRE_HEAD_ADDR; | ||
preHead.val = PRE_HEAD_ADDR; | ||
postTail.val = POST_TAIL_ADDR; | ||
} | ||
function insertTail(LinkedList storage self, bytes32 val) public { | ||
bool uniqVal = self.nodes[val].next == 0; | ||
require(uniqVal, "Unique values only !"); | ||
|
||
Node storage postTail = self.nodes[POST_TAIL_ADDR]; | ||
Node storage prev = self.nodes[postTail.prev]; | ||
Node memory node = Node(val, prev.next, postTail.prev); | ||
bytes32 nodeKey = node.val; | ||
prev.next = nodeKey; | ||
postTail.prev = nodeKey; | ||
self.nodes[val] = node; | ||
self.size += 1; | ||
} | ||
|
||
function getHead(LinkedList storage self) public view returns (bytes32) { | ||
require(self.size>0, "no items"); | ||
Node storage preHead = self.nodes[PRE_HEAD_ADDR]; | ||
bytes32 headAddr = preHead.next; | ||
Node storage head = self.nodes[headAddr]; | ||
return head.val; | ||
} | ||
|
||
function removeHead(LinkedList storage self) external { | ||
require(self.size>0, "no items"); | ||
Node storage prev = self.nodes[PRE_HEAD_ADDR]; | ||
bytes32 headAddr = prev.next; | ||
Node storage head = self.nodes[headAddr]; | ||
Node storage next = self.nodes[head.next]; | ||
prev.next = head.next; | ||
next.prev = head.prev; | ||
self.size -= 1; | ||
} | ||
|
||
function remove(LinkedList storage self, bytes32 val) external { | ||
require(self.size>0, "no items"); | ||
Node storage node = self.nodes[val]; | ||
Node storage prev = self.nodes[node.prev]; | ||
Node storage next = self.nodes[node.next]; | ||
prev.next = node.next; | ||
next.prev = node.prev; | ||
self.size -= 1; | ||
} | ||
} |
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,113 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.20; | ||
|
||
import {UserMailbox, UserMailboxInterface, Message} from "./UserMailbox.sol"; | ||
|
||
/** | ||
* @title Mailbox | ||
* @dev A contract for intermediate message exchange between parties. | ||
*/ | ||
contract Mailbox { | ||
/// account who deployed the contract | ||
address private immutable owner; | ||
|
||
/// @dev Per user Mailbox holding all messages sent by different senders | ||
mapping (address => UserMailbox) mailboxes; | ||
|
||
/// @dev Max number of messages allowed for a single Mailbox (sender,recipient) | ||
uint256 constant public MAX_MESSAGES_PER_MAILBOX = 10; | ||
|
||
/// @notice Emitted when mailbox message count changes, new message arrival or message marked as read | ||
/// @param sender The address of the message sender | ||
/// @param recipient The address of the message recipient | ||
/// @param messagesCount Total number of messages in the Mailbox for (sender,recipient) | ||
/// @param timestamp Time when operation occurred | ||
event MailboxUpdated(address indexed sender, address indexed recipient, uint messagesCount, uint256 timestamp); | ||
|
||
/// @notice Raised on attemt to write a messages to a full Mailbox | ||
error MailboxIsFull(); | ||
|
||
using UserMailboxInterface for UserMailbox; | ||
|
||
constructor() { | ||
owner = msg.sender; | ||
} | ||
|
||
/** | ||
* @notice Writes a messages to a dedicated Mailbox for (sender,recipient) | ||
* @param message The message to write | ||
* @param recipient Message recipient address | ||
*/ | ||
function writeMessage(bytes calldata message, address recipient) external { | ||
UserMailbox storage mailbox = mailboxes[recipient]; | ||
uint256 msgCount = mailbox.countMessagesFrom(msg.sender); | ||
if (msgCount == MAX_MESSAGES_PER_MAILBOX) revert MailboxIsFull(); | ||
|
||
Message memory _msg = Message({ | ||
sender: msg.sender, | ||
data: message, | ||
sentAt: block.timestamp | ||
}); | ||
mailbox.writeMessage(_msg); | ||
|
||
emit MailboxUpdated(msg.sender, recipient, msgCount+1, block.timestamp); | ||
} | ||
|
||
/** | ||
* @notice Provides a message to its recipient from the specified sender | ||
* @param sender Sender address | ||
* @return msgId Message ID | ||
* @return data The message | ||
* @return sentAt Timestamp when the message was written | ||
*/ | ||
function readMessage(address sender) external view | ||
returns (bytes32 msgId, bytes memory data, uint256 sentAt) { | ||
|
||
UserMailbox storage mailbox = mailboxes[msg.sender]; | ||
uint256 msgCount = mailbox.countMessagesFrom(sender); | ||
if (msgCount == 0) { | ||
bytes memory zero; | ||
return (bytes32(0), zero, 0); | ||
} | ||
(bytes32 _msgId, Message memory _msg) = mailbox.readMessageFrom(sender); | ||
msgId = _msgId; | ||
data = _msg.data; | ||
sentAt = _msg.sentAt; | ||
} | ||
|
||
/** | ||
* @notice Allows a recipient to read a message without specifying a sender. | ||
* Recipient is given next sender message after each read confirmation done by markMessageRead | ||
* @return msgId Message ID | ||
* @return sender address | ||
* @return data The message | ||
* @return sentAt Timestamp when the message was written | ||
*/ | ||
function readMessageNextSender() external view | ||
returns (bytes32 msgId, address sender, bytes memory data, uint256 sentAt) { | ||
UserMailbox storage mailbox = mailboxes[msg.sender]; | ||
uint256 msgCount = mailbox.countSenders(); | ||
if (msgCount == 0) { | ||
bytes memory zero; | ||
return (bytes32(0), address(0), zero, 0); | ||
} | ||
Message storage _msg; | ||
(msgId, _msg) = mailbox.readMessageNextSender(); | ||
sender = _msg.sender; | ||
data = _msg.data; | ||
sentAt = _msg.sentAt; | ||
} | ||
|
||
/** | ||
* Marks a top message as read making the next message available for reading | ||
* @param msgId ID of the read message | ||
* @return moreMessages whether other message available from the same sender | ||
*/ | ||
function markMessageRead(bytes32 msgId) external returns (bool moreMessages) { | ||
UserMailbox storage mailbox = mailboxes[msg.sender]; | ||
Message storage _msg = mailbox.getMessage(msgId); | ||
uint256 msgCount = mailbox.countMessagesFrom(_msg.sender); | ||
emit MailboxUpdated(_msg.sender, msg.sender, msgCount-1, block.timestamp); | ||
return mailbox.markMessageRead(msgId); | ||
} | ||
} |
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,82 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.20; | ||
|
||
import {LinkedList, LinkedListInterface} from "./LinkedList.sol"; | ||
|
||
struct Message { | ||
address sender; | ||
uint256 sentAt; | ||
bytes data; | ||
} | ||
struct UserMailbox { | ||
mapping (bytes32 => Message) messages; | ||
mapping (bytes32 => LinkedList) orderedMessageLists; | ||
uint256 totalMessagesCount; | ||
} | ||
|
||
bytes32 constant NEXT_SENDER_LIST_ID = keccak256("nextSender"); | ||
|
||
using LinkedListInterface for LinkedList; | ||
|
||
error MessageNotFound(); | ||
|
||
library UserMailboxInterface { | ||
function writeMessage(UserMailbox storage self, Message memory _msg) public { | ||
bytes32 messageId = keccak256(abi.encode(_msg)); | ||
self.messages[messageId] = _msg; | ||
|
||
LinkedList storage list = self.orderedMessageLists[keccak256(abi.encode(_msg.sender))]; | ||
list.init(); | ||
list.insertTail(messageId); | ||
bool isFirstSenderMsg = list.size == 1; | ||
|
||
if (isFirstSenderMsg) { | ||
LinkedList storage nextSenderList = self.orderedMessageLists[NEXT_SENDER_LIST_ID]; | ||
nextSenderList.init(); | ||
nextSenderList.insertTail(messageId); | ||
} | ||
} | ||
|
||
function readMessageFrom(UserMailbox storage self, address sender) public view returns (bytes32, Message storage) { | ||
LinkedList storage list = self.orderedMessageLists[keccak256(abi.encode(sender))]; | ||
bytes32 valHash = list.getHead(); | ||
return (valHash, self.messages[valHash]); | ||
} | ||
|
||
function readMessageNextSender(UserMailbox storage self) public view returns (bytes32, Message storage) { | ||
LinkedList storage list = self.orderedMessageLists[NEXT_SENDER_LIST_ID]; | ||
bytes32 valHash = list.getHead(); | ||
return (valHash, self.messages[valHash]); | ||
} | ||
|
||
function getMessage(UserMailbox storage self, bytes32 msgId) internal view returns (Message storage) { | ||
Message storage _msg = self.messages[msgId]; | ||
if(_msg.sentAt == 0) { | ||
revert MessageNotFound(); | ||
} | ||
return _msg; | ||
} | ||
|
||
function countMessagesFrom(UserMailbox storage self, address sender) public view returns (uint256) { | ||
return self.orderedMessageLists[keccak256(abi.encode(sender))].size; | ||
} | ||
|
||
function countSenders(UserMailbox storage self) public view returns (uint256) { | ||
return self.orderedMessageLists[NEXT_SENDER_LIST_ID].size; | ||
} | ||
|
||
function markMessageRead(UserMailbox storage self, bytes32 messageId) public returns (bool moreMessages) { | ||
Message storage _msg = self.messages[messageId]; | ||
LinkedList storage list = self.orderedMessageLists[keccak256(abi.encode(_msg.sender))]; | ||
list.remove(messageId); | ||
self.messages[messageId].sentAt=0; | ||
|
||
LinkedList storage nextSenderList = self.orderedMessageLists[NEXT_SENDER_LIST_ID]; | ||
nextSenderList.init(); | ||
nextSenderList.remove(messageId); | ||
if(list.size>0) { | ||
nextSenderList.insertTail(list.getHead()); | ||
} | ||
return list.size > 0; | ||
} | ||
} |
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,24 @@ | ||
const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules"); | ||
|
||
|
||
const userMailboxModule = buildModule("UserMailboxModule", (m) => { | ||
const linkedList = m.library("LinkedListInterface"); | ||
const userMailbox = m.contract("UserMailboxInterface", [], { | ||
libraries: { | ||
LinkedListInterface: linkedList, | ||
} | ||
}); | ||
|
||
return { userMailbox }; | ||
}); | ||
|
||
module.exports = buildModule("MailboxModule", (m) => { | ||
const {userMailbox} = m.useModule(userMailboxModule); | ||
const contract = m.contract("Mailbox", [], { | ||
libraries: { | ||
UserMailboxInterface: userMailbox | ||
} | ||
}); | ||
|
||
return { contract }; | ||
}); |
Oops, something went wrong.