From d8ef19af8cc1346fe4d15a23c8b881e55264e889 Mon Sep 17 00:00:00 2001 From: JP_ <75012004+jppoujade@users.noreply.github.com> Date: Sun, 23 Jan 2022 22:51:31 +0100 Subject: [PATCH] Add files via upload Some comments: - I've uploaded only the smart contract for this challenge. - As far as I've been able to test it, the full logic of the problem is fulfilled. - Users are able to make multiple deposits along time (their total deposits and contribution is updated when required). - Every time a reward is deposited, at the very same moment, the amount of rewards (fraction) that corresponds to every active user is computed and accumulated. In addition, total rewards are tracked in a state variable. - Users are able to make partial or full withdrawals of their currently deposited funds (if they do partial withdrawal, then they will keep receiving future rewards based on the amount they've left still deposited). - Users are able to make partial or full withdrawal of their currently accumulated rewards. - Math operations could be improved in order to avoid small rounding errors (or use OpenZeppelin safe math library) - Contract was deployed in Kovan Testnet at address: 0x684cc48e69ba9e48f2d156299e09e50f436a863c (TX initiated by 0xffc77adc943099027e182edce951550745561a9d) - Since I am far from being what is usually called as "SW developer" (just a self motivated enthusiast with an Electronic Eng. degree), right now I don't have the JS knowledge required for the development of the test procedures and other stuff... - Feedback from your side regarding the uploaded contract will be appreciated. --- ETHPool.sol | 169 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 ETHPool.sol diff --git a/ETHPool.sol b/ETHPool.sol new file mode 100644 index 00000000..df735674 --- /dev/null +++ b/ETHPool.sol @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.0; + + ///@title ETHPool Challenge + ///@author Juan Pablo Poujade (jppoujade@gmail.com) + /** + @notice ETHPool provides a service where + people can deposit ETH and they will receive weekly rewards + */ +contract ETHPool { + + // Total rewards deposited by the team + uint256 public totalRewards; + // Total amount deposited by users + uint256 public totalUsersDeposits; + + // Main users data: + struct userData { + uint256 depositedAmount; + uint256 rewardAmount; + bool isRegistered; + } + mapping(address => userData) public users; + + // Whitepages for users participating in the protocol: + address[] usersAddressList; + + // Mapping with information about the team's members addresses: + mapping(address => bool) public usersTeam; + + ///@dev Emitted when the team deposits rewards + ///@param from Address from which the rewards are sent + ///@param value ETH value deposited + ///@param date Date of the deposit + event rewardsDeposited( + address from, + uint256 value, + uint256 date + ); + + ///@dev Emitted when a user makes a new deposit + ///@param from The address of the user who made the deposit + ///@param value ETH value deposited by the user + ///@param date Date of the deposit + event userDeposited( + address from, + uint256 value, + uint256 date + ); + + ///@dev Emitted when a user makes a partial or full withdrawal of its current deposits + ///@param from The address of the user who made the withdrawal + ///@param value ETH value retired by the user + ///@param date Date of the withdrawal + event userWithdrawalDeposit( + address from, + uint256 value, + uint256 date + ); + + ///@dev Emitted when a user makes a partial or full withdrawal of its accumulated rewards + ///@param from The address of the user who made the withdrawal + ///@param value ETH value retired by the user + ///@param date Date of the withdrawal + event userWithdrawalRewards( + address from, + uint256 value, + uint256 date + ); + + // Modifier to ensure allowed operations + modifier onlyTeam() { + require( + usersTeam[msg.sender] == true, + "Only team members are allowed to execute this operation" + ); + _; + } + + // Contract constructor: + constructor() { + usersTeam[msg.sender] = true; + } + + // Function intendended for the users to make deposits into the protocol. + // The same user can make many different deposits along the time (will be accumulated) + function userDeposit() external payable { + // If this is a new user (first deposit), then we should add him to the registered/whitepages: + if (users[msg.sender].isRegistered == false) { + // Set flag as registered: + users[msg.sender].isRegistered = true; + // Add to whitepages: + usersAddressList.push(msg.sender); + } + // Update user total deposited amount: + users[msg.sender].depositedAmount += msg.value; + // Update total amount deposited by users: + totalUsersDeposits += msg.value; + // Trigger corresponding event: + emit userDeposited(msg.sender, msg.value, block.timestamp); + } + + + // Rewards deposits are expected to happen on weekly basis, but no restrictions were coded. + // In this way, additional unexpected rewards can be given by the team at any moment ;) + function depositRewards() external payable onlyTeam { + // If there are no deposits, it makes no sense to allow rewards to be deposited: + require(totalUsersDeposits > 0, "Cannot deposit rewards if there are no users deposits"); + // Update total rewards tracking variable: + totalRewards += msg.value; + // Update the amount of rewards for each active user: + // Every time a reward is deposited (at the very same moment) the fractional reward corresponding to each user is computed (based on its current deposits) + // We are iterating over a list, this is probably not an elegant nor efficient solution (gas consumption) + for (uint i=0; i= amount, "Trying to withdraw an amount higher than current deposits"); + // Update total deposited amount: + totalUsersDeposits -= amount; + // Update user deposited amount: + users[msg.sender].depositedAmount -= amount; + // Transfer funds to the user: + (bool success, ) = payable(msg.sender).call{ + value: amount + }(""); + require(success, "Transfer failed"); + emit userWithdrawalDeposit(msg.sender, amount, block.timestamp); + + } + + // Allow users to check their available rewards: + function queryUserRewards() public view returns(uint256) { + return users[msg.sender].rewardAmount; + } + + // Function intended for the user to be able to withdraw (partial or full) rewards: + function withdrawalUserReward(uint256 amount) external { + // Users should not be able to withdraw more than they currently have as rewards: + require(users[msg.sender].rewardAmount >= amount, "Trying to withdraw an amount higher than current rewards"); + // Update total deposited rewards: + totalRewards -= amount; + // Update user deposited amount: + users[msg.sender].rewardAmount -= amount; + // Transfer funds to the user: + (bool success, ) = payable(msg.sender).call{ + value: amount + }(""); + require(success, "Transfer failed"); + emit userWithdrawalRewards(msg.sender, amount, block.timestamp); + } + +} \ No newline at end of file