Contract Source Code:
<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract Twitter {
uint16 public MAX_TWEET_LENGTH = 280;
uint8 public MIN_TWEET_LENGTH = 1;
address public owner;
constructor() {
owner = msg.sender;
}
struct Tweet {
uint256 id;
address author;
string content;
uint256 timestamp;
}
struct Like {
uint256 id;
address author;
uint256 timestamp;
}
struct Comment {
uint256 id;
address author;
string content;
uint256 timestamp;
}
mapping(uint256 => Tweet) public tweets;
uint256[] public tweetIds;
mapping(address => string) public userNames;
mapping(uint256 => Like[]) public tweetLikes;
mapping(uint256 => mapping(address => bool)) public tweetLikedBy;
mapping(uint256 => mapping(address => uint256)) public tweetLikeIndex;
mapping(uint256 => Comment[]) public tweetComments;
mapping(uint256 => uint256) public commentToTweet;
mapping(uint256 => uint256) public commentIndex;
uint256 public tweetCounter = 1;
uint256 public likeCounter = 1;
uint256 public commentCounter = 1;
event UserNameSet(address indexed user, string name);
event TweetCreated(uint256 tweetId, address author, string content, uint256 timestamp);
event TweetLiked(uint256 tweetId, address author, uint256 timestamp);
event TweetUnliked(uint256 tweetId, address author, uint256 timestamp);
event CommentCreated(uint256 tweetId, uint256 commentId, address author, string content, uint256 timestamp);
event CommentDeleted(uint256 tweetId, uint256 commentId, address author, uint256 timestamp);
event TweetDeleted(uint256 tweetId);
modifier validTweetLength(string memory _tweet) {
require(bytes(_tweet).length >= MIN_TWEET_LENGTH, "Tweet cannot be empty");
require(bytes(_tweet).length <= MAX_TWEET_LENGTH, "Tweet cannot exceed 280 characters");
_;
}
modifier onlyOwner() {
require(msg.sender == owner, "Only owner can call this function");
_;
}
function setUserName(string calldata _name) external {
// Check any other user doesn't have the same name.
require(bytes(userNames[msg.sender]).length == 0, "User name already set");
for (uint256 i = 1; i < tweetCounter; i++) {
if (keccak256(bytes(userNames[tweets[i].author])) == keccak256(bytes(_name))) {
revert("Username already taken");
}
}
userNames[msg.sender] = _name;
emit UserNameSet(msg.sender, _name);
}
function getUserName(address _user) public view returns (string memory) {
return userNames[_user];
}
function changeTweetMaxandMinLength(uint16 _maxTweetLength, uint8 _minTweetLength) public onlyOwner {
MAX_TWEET_LENGTH = _maxTweetLength;
MIN_TWEET_LENGTH = _minTweetLength;
}
function createTweet(string memory _tweet) public validTweetLength(_tweet) {
uint256 currentTweetId = tweetCounter;
tweets[currentTweetId] = Tweet({
id: currentTweetId,
author: msg.sender,
content: _tweet,
timestamp: block.timestamp
});
tweetIds.push(currentTweetId);
tweetCounter++;
emit TweetCreated(currentTweetId, msg.sender, _tweet, block.timestamp);
}
function getTweetOfSpecificUser(address _user) public view returns (Tweet[] memory) {
uint256 count = 0;
for (uint256 i = 0; i < tweetIds.length; i++) {
if (tweets[tweetIds[i]].author == _user) {
count++;
}
}
Tweet[] memory userTweets = new Tweet[](count);
uint256 index = 0;
for (uint256 i = 0; i < tweetIds.length; i++) {
if (tweets[tweetIds[i]].author == _user) {
userTweets[index] = tweets[tweetIds[i]];
index++;
}
}
return userTweets;
}
function getPaginatedTweetIds(uint256 page, uint256 pageSize) public view returns (uint256[] memory) {
uint256 totalTweets = tweetIds.length;
if (totalTweets == 0) return new uint256[](0);
// Adjust 'page' so that the first page starts from 1 (not 0)
uint256 start = (page - 1) * pageSize;
// If the start index exceeds the total tweet count, return empty array
if (start >= totalTweets) return new uint256[](0);
uint256 end = start + pageSize;
// Make sure the 'end' doesn't go beyond the available tweets
if (end > totalTweets) {
end = totalTweets;
}
// Calculate the actual number of tweets to return
uint256 count = end - start;
// Allocate memory for the paginated IDs array
uint256[] memory paginatedIds = new uint256[](count);
// Copy the tweet IDs from the correct indices
for (uint256 i = 0; i < count; i++) {
paginatedIds[i] = tweetIds[start + i];
}
return paginatedIds;
}
function getTweetDetails(uint256 _tweetId)
public
view
returns (
uint256,
address,
string memory,
uint256,
uint256,
uint256
)
{
require(_tweetId > 0 && _tweetId < tweetCounter, "Tweet not found");
Tweet memory tweet = tweets[_tweetId];
uint256 likesCount = tweetLikes[_tweetId].length;
uint256 commentsCount = tweetComments[_tweetId].length;
return (tweet.id, tweet.author, tweet.content, tweet.timestamp, likesCount, commentsCount);
}
function getTweetLikes(uint256 _tweetId)
public
view
returns (
uint256[] memory,
address[] memory,
uint256[] memory
)
{
require(_tweetId > 0 && _tweetId < tweetCounter, "Tweet not found");
uint256 likeCount = tweetLikes[_tweetId].length;
uint256[] memory ids = new uint256[](likeCount);
address[] memory authors = new address[](likeCount);
uint256[] memory timestamps = new uint256[](likeCount);
for (uint256 i = 0; i < likeCount; i++) {
Like memory likeItem = tweetLikes[_tweetId][i];
ids[i] = likeItem.id;
authors[i] = likeItem.author;
timestamps[i] = likeItem.timestamp;
}
return (ids, authors, timestamps);
}
function getRandomCommentIds(uint256 _tweetId, uint256 count) public view returns (uint256[] memory) {
require(_tweetId > 0 && _tweetId < tweetCounter, "Tweet not found");
uint256 totalComments = tweetComments[_tweetId].length;
if (totalComments == 0) return new uint256[](0);
if (count > totalComments) count = totalComments;
uint256[] memory ids = new uint256[](totalComments);
for (uint256 i = 0; i < totalComments; i++) {
ids[i] = tweetComments[_tweetId][i].id;
}
// Fisher-Yates shuffle.
for (uint256 i = totalComments - 1; i > 0; i--) {
uint256 j = uint256(keccak256(abi.encodePacked(block.timestamp, msg.sender, i))) % (i + 1);
(ids[i], ids[j]) = (ids[j], ids[i]);
}
uint256[] memory selectedIds = new uint256[](count);
for (uint256 i = 0; i < count; i++) {
selectedIds[i] = ids[i];
}
return selectedIds;
}
function getCommentDetails(uint256 _commentId) public view returns (uint256, address, string memory, uint256) {
require(_commentId > 0 && _commentId < commentCounter, "Comment not found");
uint256 tweetIdOfComment = commentToTweet[_commentId];
uint256 index = commentIndex[_commentId];
Comment memory comment = tweetComments[tweetIdOfComment][index];
return (comment.id, comment.author, comment.content, comment.timestamp);
}
function likeTweet(uint256 _tweetId) external {
require(_tweetId > 0 && _tweetId < tweetCounter, "Tweet does not exist");
require(!tweetLikedBy[_tweetId][msg.sender], "User has already liked this tweet");
uint256 currentLikeId = likeCounter;
tweetLikes[_tweetId].push(Like({
id: currentLikeId,
author: msg.sender,
timestamp: block.timestamp
}));
// Save the index of the like.
tweetLikeIndex[_tweetId][msg.sender] = tweetLikes[_tweetId].length - 1;
tweetLikedBy[_tweetId][msg.sender] = true;
likeCounter++;
emit TweetLiked(_tweetId, msg.sender, block.timestamp);
}
function unlikeTweet(uint256 _tweetId) external {
require(_tweetId > 0 && _tweetId < tweetCounter, "Tweet does not exist");
require(tweetLikedBy[_tweetId][msg.sender], "User hasn't liked this tweet");
uint256 index = tweetLikeIndex[_tweetId][msg.sender];
uint256 lastIndex = tweetLikes[_tweetId].length - 1;
if (index != lastIndex) {
Like memory lastLike = tweetLikes[_tweetId][lastIndex];
tweetLikes[_tweetId][index] = lastLike;
tweetLikeIndex[_tweetId][lastLike.author] = index;
}
tweetLikes[_tweetId].pop();
tweetLikedBy[_tweetId][msg.sender] = false;
delete tweetLikeIndex[_tweetId][msg.sender];
emit TweetUnliked(_tweetId, msg.sender, block.timestamp);
}
function commentOnTweet(uint256 _tweetId, string memory _comment) external {
require(_tweetId > 0 && _tweetId < tweetCounter, "Tweet does not exist");
uint256 currentCommentId = commentCounter;
tweetComments[_tweetId].push(Comment({
id: currentCommentId,
author: msg.sender,
content: _comment,
timestamp: block.timestamp
}));
// Save lookup info for the comment.
commentToTweet[currentCommentId] = _tweetId;
commentIndex[currentCommentId] = tweetComments[_tweetId].length - 1;
commentCounter++;
emit CommentCreated(_tweetId, currentCommentId, msg.sender, _comment, block.timestamp);
}
function deleteComment(uint256 _commentId) external {
require(_commentId > 0 && _commentId < commentCounter, "Comment does not exist");
uint256 tweetIdOfComment = commentToTweet[_commentId];
uint256 index = commentIndex[_commentId];
require(tweetComments[tweetIdOfComment][index].author == msg.sender, "Not the comment owner");
uint256 lastIndex = tweetComments[tweetIdOfComment].length - 1;
if (index != lastIndex) {
Comment memory lastComment = tweetComments[tweetIdOfComment][lastIndex];
tweetComments[tweetIdOfComment][index] = lastComment;
commentIndex[lastComment.id] = index;
}
tweetComments[tweetIdOfComment].pop();
// Clean up lookup mappings.
delete commentToTweet[_commentId];
delete commentIndex[_commentId];
emit CommentDeleted(tweetIdOfComment, _commentId, msg.sender, block.timestamp);
}
function deleteTweet(uint256 _tweetId) public {
require(_tweetId > 0 && _tweetId < tweetCounter, "Tweet does not exist");
require(tweets[_tweetId].author == msg.sender, "You can't delete this tweet");
// Optional: Clean up associated likes and comments.
delete tweetLikes[_tweetId];
delete tweetComments[_tweetId];
// Remove the tweet from the tweets mapping.
delete tweets[_tweetId];
// Remove _tweetId from tweetIds array via swap-pop.
uint256 length = tweetIds.length;
for (uint256 i = 0; i < length; i++) {
if (tweetIds[i] == _tweetId) {
tweetIds[i] = tweetIds[length - 1];
tweetIds.pop();
break;
}
}
emit TweetDeleted(_tweetId);
}
}