E1: EthWallet.sol

Create a smart contract with a few functions demonstrating different visibility levels (public, private, internal, external) and return values and includes a custom modifier.

Token Wallet with Withdrawal Limits

Use Case Overview: Implement a smart contract that acts as a wallet for a specific token. This contract would allow users to deposit, withdraw, and check their token balance, with daily withdrawal limits for security.

  • Functions:

    • depositTokens: A public function to deposit tokens into the wallet.

    • withdrawTokens: An external function to withdraw a specified amount of tokens, adhering to daily limits.

    • getBalance: A public view function to check the token balance of a user.

  • Modifiers:

    • withdrawalLimitCheck: A custom modifier to enforce the daily withdrawal limit per user.

    • sufficientBalance: A modifier to ensure the user has enough tokens for withdrawal requests.

These use cases involve various function types and custom modifiers, providing a comprehensive understanding of how these elements can be combined to build functional and secure smart contracts. You can further explore these ideas by considering aspects like security, efficiency, and user interaction within the smart contract ecosystem.

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/* errors */
error EthWallet__DepositMustBeAboveZero();
error EthWallet__WithdrawalMustBeAboveZero();
error EthWallet__WalletIsEmpty();
error EthWallet__WithdrawalExceedsUserBalance();
error EthWallet_InsufficientContractBalance();

contract EthWallet {
    /* structs */
    struct User {
        address ownerAddress;
        uint userBalance;
        uint withdrawnToday;
        uint lastWithdrawalTime;
    }

    /* mappings */
    mapping(address => User) private users;

    /* state variables */
    uint public constant dailyWithdrawalLimit = 10 ether;
    bool private locked;
    uint currentBalance;

    modifier withdrawalLimitCheck(uint _withdrawalAmount) {
        User storage currentUser = users[msg.sender];

        // Reset withdrawal amount if a new day has started
        if (isNewDay(currentUser.lastWithdrawalTime)) {
            currentUser.withdrawnToday = 0;
        }

        // Check if the requested withdrawal exceeds the daily limit
        require(
            currentUser.withdrawnToday + _withdrawalAmount <=
                dailyWithdrawalLimit,
            "Daily withdrawal limit exceeded"
        );

        // Update user's withdrawal data
        currentUser.withdrawnToday += _withdrawalAmount;
        currentUser.lastWithdrawalTime = block.timestamp;
        _;
    }

    modifier sufficientBalance(uint _withdrawalAmount) {
        uint withdrawalAmount = _withdrawalAmount;
        currentBalance = users[msg.sender].userBalance;
        if (currentBalance == 0) {
            revert EthWallet__WalletIsEmpty();
        } else if (_withdrawalAmount > address(this).balance) {
            revert EthWallet_InsufficientContractBalance();
        } else if (_withdrawalAmount > currentBalance) {
            revert EthWallet__WithdrawalExceedsUserBalance();
        }
        _;
    }

    modifier noReeentrant() {
        require(!locked, "No re_entrancy");
        locked = true;
        _;
        locked = false;
    }

    // Helper function to determine if a new day has started
    function isNewDay(uint lastWithdrawalTime) internal view returns (bool) {
        uint currentDay = block.timestamp / 1 days;
        uint lastWithdrawalDay = lastWithdrawalTime / 1 days;
        return currentDay > lastWithdrawalDay;
    }

    function deposit() public payable {
        uint depositAmount = msg.value;

        if (depositAmount <= 0) {
            revert EthWallet__DepositMustBeAboveZero();
        }

        if (users[msg.sender].userBalance > 0) {
            users[msg.sender].userBalance += depositAmount;
        } else {
            users[msg.sender] = User(msg.sender, depositAmount, 0, 0);
        }
    }

    function withdraw(
        uint _withdrawalAmount
    )
        external
        withdrawalLimitCheck(_withdrawalAmount)
        sufficientBalance(_withdrawalAmount)
        noReeentrant
    {
        if (_withdrawalAmount <= 0) {
            revert EthWallet__WithdrawalMustBeAboveZero();
        }

        // Update user Balance
        users[msg.sender].userBalance -= _withdrawalAmount;

        // Process withdrawal
        payable(msg.sender).transfer(_withdrawalAmount);
    }

    function getUserBalance() public view returns (uint) {
        if (users[msg.sender].ownerAddress != address(0)) {
            return users[msg.sender].userBalance;
        } else {
            return 0;
        }
    }
}

Feedback

As usual I prompt for feedback and after go through each improvement one by one. The above contract is the final contract.

ChatGpt

Your EthWallet smart contract is well-structured and covers several important aspects of an ETH wallet, including deposit and withdrawal functions, daily withdrawal limits, and basic user management. Here's some feedback and suggestions for potential improvements:

Positives

  1. Clear Structure: The contract is well-organized with clearly defined structs, mappings, modifiers, and functions.

  2. Use of Modifiers: The use of modifiers for withdrawal limits and balance checks is a good practice for code reusability and readability.

  3. Withdrawal Limit Implementation: The implementation of a daily withdrawal limit with a reset mechanism based on days is a thoughtful feature.

Suggestions for Improvement

  1. Redundant Balance Checks: The withdraw function has multiple balance checks (sufficientBalance modifier and checks within the function itself). Consolidating these checks to avoid redundancy could improve efficiency.

  2. Zero Withdrawal Amount Check: The check for zero withdrawal amount (if (_withdrawalAmount <= 0)) could be moved to the beginning of the withdraw function for efficiency, as it's a basic validation.

  3. Wallet Status Check: Consider whether the walletStatus flag is necessary. If a user's balance is zero, does it mean the wallet doesn't exist, or could it simply mean the wallet is empty? Simplifying this logic might reduce complexity.

  4. State Variable Usage: The state variables currentBalance and withdrawalAmount seem redundant since their values are always derived from local context. It might be more efficient to use local variables within functions instead.

  5. Fallback and Receive Functions: Implementing fallback() and/or receive() functions would allow the contract to receive Ether sent directly to the contract's address (without calling deposit).

  6. Event Logging: Consider emitting events for important actions like deposits and withdrawals. This would help in tracking transactions and changes in state.

  7. Security Considerations:

    • Reentrancy Guard: Add reentrancy protection for the withdraw function to prevent potential attacks.

    • Check-Effects-Interactions Pattern: Rearrange the withdraw function to follow this pattern, where state changes (userBalance update) occur before external calls (transfer).

  8. Error Messages: While the revert statements with error messages are good for debugging, ensure they are concise and clear for better gas efficiency and readability.

  9. Gas Optimization: Review the contract for potential gas optimizations. For instance, storing block.timestamp / 1 days in a local variable in isNewDay could save gas if the division is used multiple times.

  10. Function Visibility: The isNewDay function is private and only used within the contract. If this logic might be useful for other contracts in the future, consider making it internal.

  11. Testing and Auditing: Thoroughly test the contract, especially the withdrawal logic and daily limit reset mechanism, and consider a professional audit for security, especially since this contract handles real value.

Your contract shows a solid understanding of Solidity and Ethereum smart contract development. These suggestions are aimed at refining the contract further, optimizing its efficiency, and ensuring its security and robustness.

Last updated