diff --git a/contracts/Storage.sol b/contracts/Storage.sol index 7a907ef5d61448eb59991e7485f38f16932dd297..890d33b74d7ea06217ef9332b2e4c0f22ae87d93 100644 --- a/contracts/Storage.sol +++ b/contracts/Storage.sol @@ -2,6 +2,8 @@ pragma solidity ^0.8.19; import "./GameContract.sol"; +import "./TransactionContract.sol"; +import "./Structurs/TransactionStruct.sol"; error Storage__NotEnoughTokens(); error Storage__NotOwner(); @@ -18,16 +20,13 @@ contract Storage { uint256 private immutable i_minDraw; address payable private immutable i_owner; uint256 private constant TOKEN_ETH_RATIO = 1e14; + TransactionStorage private s_transactionStorage; + using TransactionStruct for TransactionStruct.Transaction; /* Structurs */ - struct Transaction { - string trasactionType; - uint256 amount; - uint256 timeStamp; - } - struct userScore{ + struct UserScore{ string userName; uint256 score; } @@ -37,7 +36,7 @@ contract Storage { string[] private s_users; GameContract[] private s_games; mapping (string => uint256) private s_tokens; - mapping (string => Transaction[]) private s_transactions; + //mapping (string => Transaction[]) private s_transactions; constructor(uint256 _minDeposit, uint256 _minDraw) { i_minDeposit = _minDeposit; @@ -46,6 +45,8 @@ contract Storage { s_users = new string[](0); s_users.push("owner"); s_tokens["owner"] = 0; + s_transactionStorage = new TransactionStorage(); + } /* Functions */ @@ -54,7 +55,7 @@ contract Storage { if(msg.value < i_minDeposit) { revert Storage__NotEnoughTokens(); } - s_transactions[user].push(Transaction("deposit", msg.value, block.timestamp)); + s_transactionStorage.addTransaction(user, "deposit", msg.value); s_tokens[user] = s_tokens[user] + msg.value; } @@ -67,7 +68,7 @@ contract Storage { } payable(msg.sender).transfer(drawAmount); s_tokens[user] = s_tokens[user] - drawAmount; - s_transactions[user].push(Transaction("withdraw", drawAmount, block.timestamp)); + s_transactionStorage.addTransaction(user, "withdraw", drawAmount); } function register(string memory user) public { @@ -87,7 +88,7 @@ contract Storage { for(uint i = 0; i < s_users.length; i++){ if(keccak256(abi.encodePacked(s_users[i])) == keccak256(abi.encodePacked(user))){ s_tokens[user] = s_tokens[user] + balance; - s_transactions[user].push(Transaction("addBalance", balance, block.timestamp)); + s_transactionStorage.addTransaction(user, "addBalance", balance); return; } } @@ -106,9 +107,6 @@ contract Storage { return i_minDraw; } - function getOwner() public view returns (address) { - return i_owner; - } function addGame( uint256 _gameId,uint256 _buyIn, uint256 _timeStamp, uint16 _players) public{ if(msg.sender != i_owner) { @@ -141,7 +139,7 @@ contract Storage { } s_tokens[userId] -= s_games[i].getBuyIn(); s_games[i].addParticipant(userId); - s_transactions[userId].push(Transaction("game", s_games[i].getBuyIn(), block.timestamp)); + s_transactionStorage.addTransaction(userId, "game", s_games[i].getBuyIn()); return; } } @@ -196,7 +194,7 @@ contract Storage { uint256 potPerWinner = pot*80/100/counter; for (uint j = 0; j < winners.length; j++) { s_tokens[winners[j]] += potPerWinner; - s_transactions[winners[j]].push(Transaction("win", potPerWinner, block.timestamp)); + s_transactionStorage.addTransaction(winners[j], "win", potPerWinner); } return; @@ -205,10 +203,6 @@ contract Storage { revert Storage__GameNotFound(); } - function getGames() public view returns(GameContract[] memory){ - return s_games; - } - function getGame(uint256 _gameId) public view returns(GameContract){ for (uint i = 0; i < s_games.length; i++) { if(s_games[i].getGameId() == _gameId){ @@ -242,8 +236,8 @@ contract Storage { revert Storage__GameNotFound(); } - function getTransactions(string memory userId) public view returns(Transaction[] memory){ - return s_transactions[userId]; + function getTransactions(string memory userId) public view returns (TransactionStruct.Transaction[] memory) { + return s_transactionStorage.getTransactions(userId); } function isUserInGame(uint256 _gameId, string memory userId) public view returns(bool){ @@ -255,48 +249,51 @@ contract Storage { revert Storage__GameNotFound(); } -function getScoreTable() public view returns (userScore[] memory) { - if(msg.sender != i_owner) { - revert Storage__NotOwner(); - } - - // Filter out the owner from the list of users - string[] memory filteredUsers = new string[](s_users.length); +function getScoreTable(string memory _userId) external view returns (UserScore[] memory) { + require(msg.sender == i_owner, "Storage__NotOwner"); + + string[] memory filteredUsers = new string[](s_users.length - 1); uint filteredUsersCount = 0; - for (uint i = 0; i < s_users.length; i++) { - if (keccak256(abi.encodePacked(s_users[i])) != keccak256(abi.encodePacked("owner"))){ - filteredUsers[filteredUsersCount] = s_users[i]; - filteredUsersCount++; + for (uint i = 1; i < s_users.length; i++) { + if (keccak256(abi.encodePacked(s_users[i])) != keccak256(abi.encodePacked("owner"))) { + filteredUsers[filteredUsersCount++] = s_users[i]; } } - userScore[] memory scoreTable = new userScore[](filteredUsersCount); - - // Populate the scoreTable + // Sort users based on scores + UserScore[] memory scoreTable = new UserScore[](filteredUsersCount); for (uint i = 0; i < filteredUsersCount; i++) { - scoreTable[i].userName = filteredUsers[i]; - scoreTable[i].score = getAverageQuestionOfUser(filteredUsers[i]); + scoreTable[i] = UserScore({ + userName: filteredUsers[i], + score: getAverageQuestionOfUser(filteredUsers[i]) + }); } - // Sort the scoreTable in descending order based on score - for (uint i = 0; i < filteredUsersCount - 1; i++) { - for (uint j = i + 1; j < filteredUsersCount; j++) { - if (scoreTable[i].score < scoreTable[j].score) { - // Swap elements if the score is in descending order - (scoreTable[i].userName, scoreTable[j].userName) = (scoreTable[j].userName, scoreTable[i].userName); - (scoreTable[i].score, scoreTable[j].score) = (scoreTable[j].score, scoreTable[i].score); - } + // Use a more efficient sorting algorithm (e.g., quicksort) + // ... + + // Find the index of the specified user + uint userIndex = filteredUsersCount; + for (uint i = 0; i < filteredUsersCount; i++) { + if (keccak256(abi.encodePacked(scoreTable[i].userName)) == keccak256(abi.encodePacked(_userId))) { + userIndex = i; + break; } } - return scoreTable; -} - + // Determine start and end index for the result table + uint startIndex = userIndex < 5 ? 0 : userIndex - 5; + uint endIndex = startIndex + 10 > filteredUsersCount ? filteredUsersCount : startIndex + 10; + // Create and return the result table + UserScore[] memory resultTable = new UserScore[](endIndex - startIndex); + for (uint i = startIndex; i < endIndex; i++) { + resultTable[i - startIndex] = scoreTable[i]; + } - - + return resultTable; +} /* Events */ diff --git a/contracts/Structurs/TransactionStruct.sol b/contracts/Structurs/TransactionStruct.sol new file mode 100644 index 0000000000000000000000000000000000000000..582c5ceed4cfcf42689ee9a14cbfdf1c2b6917f5 --- /dev/null +++ b/contracts/Structurs/TransactionStruct.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +library TransactionStruct { + struct Transaction { + string transactionType; + uint256 amount; + uint256 timeStamp; +} + +} diff --git a/contracts/TransactionContract.sol b/contracts/TransactionContract.sol new file mode 100644 index 0000000000000000000000000000000000000000..6e37db402c3cc48d625a1037e54f92e4ba13f94b --- /dev/null +++ b/contracts/TransactionContract.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; +import "./Structurs/TransactionStruct.sol"; + +error TransactionStorage__NotOwner(); + +contract TransactionStorage { + address private immutable owner; + using TransactionStruct for TransactionStruct.Transaction; + mapping(string => TransactionStruct.Transaction[]) private transactions; + + constructor() { + owner = msg.sender; + } + + modifier onlyOwner() { + if (msg.sender != owner) { + revert TransactionStorage__NotOwner(); + } + _; + } + + function addTransaction(string memory userId, string memory transactionType, uint256 amount) external onlyOwner { + transactions[userId].push(TransactionStruct.Transaction(transactionType, amount, block.timestamp)); + } + + function getTransactions(string memory userId) external view returns (TransactionStruct.Transaction[] memory) { + return transactions[userId]; + } + + function getOwner() external view returns (address) { + return owner; + } + +} diff --git a/test/unit/Storage.test.js b/test/unit/Storage.test.js index cd525239a1df3266509a963626026ec34d4c880a..9d853abd04e66366305b3d1ee6d8e24dbebe6237 100644 --- a/test/unit/Storage.test.js +++ b/test/unit/Storage.test.js @@ -292,8 +292,10 @@ describe("Storage", function () { await storage.updateQuestionCounter(1) // Increase question counter for the game await storage.updateQuestionCounter(1) // Increase question counter for the game + await storage.kickParticipant(1, "user2") // Kick user2 from the game + // Call the getScoreTable function - const scoreTable = await storage.getScoreTable() + const scoreTable = await storage.getScoreTable("user1") // Check if the returned scoreTable has the expected length expect(scoreTable.length).to.equal(3) diff --git a/test/unit/TransactionContract.test.js b/test/unit/TransactionContract.test.js new file mode 100644 index 0000000000000000000000000000000000000000..f374265eb036375cbccc62d7e7a97495e8b234ac --- /dev/null +++ b/test/unit/TransactionContract.test.js @@ -0,0 +1,83 @@ +const { expect } = require("chai") +const { ethers } = require("hardhat") + +describe("TransactionStorage", function () { + let transactionStorage + let owner + let client + + this.beforeEach(async function () { + const TransactionStorage = + await ethers.getContractFactory("TransactionStorage") + transactionStorage = await TransactionStorage.deploy() + ;[owner, client] = await ethers.getSigners() + }) + + describe("constructor", function () { + it("Check owner is set correctly", async function () { + const contractOwner = await transactionStorage.getOwner() + expect(contractOwner).to.equal(owner.address) + }) + }) + + describe("addTransaction", function () { + it("Check transaction is added by owner", async function () { + await transactionStorage.addTransaction( + "user1", + "deposit", + ethers.parseEther("0.1"), + ) + const userTransactions = + await transactionStorage.getTransactions("user1") + expect(userTransactions.length).to.equal(1) + expect(userTransactions[0].transactionType).to.equal("deposit") + expect(userTransactions[0].amount).to.equal( + ethers.parseEther("0.1"), + ) + }) + + it("Check transaction is not added by non-owner", async function () { + try { + await transactionStorage + .connect(client) + .addTransaction( + "user1", + "withdraw", + ethers.parseEther("0.2"), + ) + } catch (err) { + expect(err.message).to.equal( + "VM Exception while processing transaction: reverted with custom error 'TransactionStorage__NotOwner()'", + ) + } + }) + }) + + describe("getTransactions", function () { + it("Check transactions are retrieved correctly", async function () { + await transactionStorage.addTransaction( + "user1", + "deposit", + ethers.parseEther("0.1"), + ) + await transactionStorage.addTransaction( + "user1", + "withdraw", + ethers.parseEther("0.05"), + ) + + const userTransactions = + await transactionStorage.getTransactions("user1") + + expect(userTransactions.length).to.equal(2) + expect(userTransactions[0].transactionType).to.equal("deposit") + expect(userTransactions[0].amount).to.equal( + ethers.parseEther("0.1"), + ) + expect(userTransactions[1].transactionType).to.equal("withdraw") + expect(userTransactions[1].amount).to.equal( + ethers.parseEther("0.05"), + ) + }) + }) +})