Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add Vesting struct to IMergeV2 interface #82

Closed
wants to merge 27 commits into from

Conversation

bitcoinbrisbane
Copy link
Contributor

@bitcoinbrisbane bitcoinbrisbane commented Oct 22, 2024

This commit adds a new struct called Vesting to the IMergeV2 interface. The Vesting struct has two properties: amount and end. This struct will be used to track vested amounts of tokens.

Co-authored-by: [Author Name]

Summary by CodeRabbit

  • New Features

    • Introduced the DynamicEater smart contract for managing token vesting and transfers.
    • Added functions for users to claim vested tokens and check their balances.
    • Implemented a merging feature allowing users to merge tokens and sell them via an Automated Market Maker (AMM).
    • Added event notifications for significant actions like token merging and rate changes.
    • Introduced a new deployment module for the DynamicEater contract.
    • Added a new smart contract UniswapV3ViaRouterFomo for facilitating token swaps on the Uniswap V3 platform.
  • Bug Fixes

    • Enhanced security measures and access control features.
  • Documentation

    • Updated deployment scripts to include the new DynamicEater module.
  • Tests

    • Expanded test cases for the DynamicEater contract, focusing on dynamic rates and large deposit scenarios.

This commit adds a new struct called Vesting to the IMergeV2 interface. The Vesting struct has two properties: amount and end. This struct will be used to track vested amounts of tokens.

Co-authored-by: [Author Name]
@bitcoinbrisbane bitcoinbrisbane linked an issue Oct 22, 2024 that may be closed by this pull request
4 tasks
Copy link
Contributor

coderabbitai bot commented Oct 22, 2024

Walkthrough

The changes in this pull request primarily involve the introduction of a new smart contract named DynamicEater, which implements token vesting and transfer functionalities. The contract utilizes OpenZeppelin libraries for security and includes various functions for managing token rates, balances, and vesting periods. Additionally, several related files have been removed, including the MergeFactory and MergeWithMarket contracts, indicating a shift in the project's structure. New deployment scripts and test cases have been added to support the DynamicEater contract.

Changes

File Change Summary
contracts/core/merge/DynamicEater.sol Added DynamicEater contract with functions for token vesting, transfers, and management. Introduced Vesting struct and various modifiers and events.
contracts/interfaces/IMergeV2.sol Added new Vesting struct with amount and end fields.
ignition/deployments/chain-8453/artifacts/MergeFactoryModule#MergeFactory.json Removed MergeFactory contract artifact.
ignition/deployments/chain-8453/artifacts/MergeWithMarket.json Removed MergeWithMarket contract artifact.
ignition/deployments/chain-8453/deployed_addresses.json Updated to remove entries for old modules and added entry for DynamicEater.
ignition/modules/DynamicEaterModule.ts Added module for deploying DynamicEater in Hardhat environment.
test/DynamicEater.test.ts Enhanced testing framework for DynamicEater with new test cases.
package.json Updated scripts section to include new deployment script for DynamicEater and modified existing prettier scripts.
ignition/deployments/chain-8453/artifacts/DynamicEaterModule#DynamicEater.json Added artifact for DynamicEater contract with ABI and constructor details.
ignition/deployments/chain-8453/artifacts/DynamicEaterModule#DynamicEater.dbg.json Added debug information file for DynamicEater.

Possibly related PRs

  • 75 vesting period #76: This PR introduces a Vesting struct and modifies the Eater contract to manage token vesting, which is directly related to the Vesting struct added in the main PR.
  • Refactor Eater and GenericMerge contracts #80: This PR refactors the Eater and GenericMerge contracts, including changes to the vestingDuration variable and the introduction of a whenSolvent modifier, which aligns with the vesting logic and functionalities introduced in the main PR.

Suggested labels

SC

Suggested reviewers

  • hippocampus-web3

🐰 In the meadow, bright and clear,
A new contract brings us cheer!
DynamicEater, tokens to claim,
With vesting logic, it plays the game.
Old modules gone, a fresh new start,
Hopping forward, we take part! 🌼


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@bitcoinbrisbane bitcoinbrisbane marked this pull request as draft October 22, 2024 05:28
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 21

🧹 Outside diff range and nitpick comments (8)
contracts/interfaces/IMergeV2.sol (3)

4-7: LGTM! Consider adding inline documentation.

The Vesting struct is well-defined with appropriate field types. It effectively captures the essential information for token vesting.

Consider adding inline documentation to clarify the purpose of each field:

/// @dev Represents vesting information for tokens
struct Vesting {
    uint256 amount; /// @dev The amount of tokens vested
    uint256 end;    /// @dev The timestamp when the vesting period ends
}

Line range hint 10-16: Consider extending the interface to support vesting operations.

While the Vesting struct has been added, the IMergeV2 interface doesn't include any methods to interact with vesting information. Consider adding methods to fully utilize the new struct.

Here are some suggestions for new methods:

interface IMergeV2 {
    // ... existing methods ...

    /// @dev Get the vesting information for a user
    function getVesting(address user) external view returns (Vesting memory);

    /// @dev Update the vesting information for a user
    function updateVesting(address user, uint256 amount, uint256 end) external;

    /// @dev Check if tokens are vested for a user
    function isVested(address user) external view returns (bool);

    /// @dev Get the total vested amount for a user
    function getVestedAmount(address user) external view returns (uint256);
}

These additions would provide a comprehensive set of operations for managing vesting within the interface.


Line range hint 1-16: Summary: New Vesting struct added, consider full integration with interface

The addition of the Vesting struct is a good start for implementing vesting functionality. However, the IMergeV2 interface hasn't been updated to fully utilize this new struct.

Next steps to consider:

  1. Add inline documentation to the Vesting struct as suggested earlier.
  2. Extend the IMergeV2 interface with methods to interact with vesting information, as proposed in the previous comment.
  3. Ensure that the implementation of these new methods aligns with the overall architecture and security considerations of the contract system.
  4. Update any related contracts or tests to reflect these changes in the interface.

These steps will help to fully integrate the vesting functionality into your contract system.

ignition/modules/VultMerge.ts (2)

6-11: LGTM: Module structure is correct. Consider making vesting period configurable.

The module definition, contract instantiation, and export are implemented correctly. However, to improve flexibility, consider making the vesting period configurable, similar to the address constants.

Example refactor:

export default buildModule("VultMergeModule", m => {
  const vestingPeriod = parseInt(process.env.VESTING_PERIOD || "7", 10);
  const merge = m.contract("VultMerge", [WEWE_ADDRESS, VULT_IOU_ADDRESS, vestingPeriod]);

  return { merge };
});

This change allows the vesting period to be set via an environment variable, defaulting to 7 if not specified.


1-11: Overall assessment: Well-structured module with room for improved configurability.

The VultMergeModule is correctly implemented using Hardhat Ignition's buildModule function. It properly sets up the VultMerge contract with the necessary parameters. To enhance the module's flexibility and maintainability, consider implementing the suggested changes regarding the use of environment variables for addresses and making the vesting period configurable.

These improvements will make the deployment process more adaptable to different environments and easier to maintain in the long run.

contracts/core/merge/DynamicEater.sol (1)

51-51: Clarify the comment regarding vesting logic

The comment // If transfer, dont vest seems misleading. The condition if (vestingDuration != 0) checks if vesting is enabled, and if so, it updates the vesting records.

Consider updating the comment for clarity:

-// If transfer, dont vest
+// If vesting is enabled, update vesting records
contracts/core/merge/VultMerge.sol (2)

95-95: Clarify Error Message in require Statement

The error message "Insufficient balance to eat" may be confusing in the context of the mergeAndSell function. Consider updating the message to better reflect the action being performed.

Apply this diff to improve the error message:

-        require(balance >= amount, "VultMerge: Insufficient balance to eat");
+        require(balance >= amount, "VultMerge: Insufficient balance to merge and sell");

109-109: Clarify Error Message in require Statement

The error message "Insufficient balance to eat" might not clearly convey the issue in the merge function. Updating it can enhance user understanding.

Apply this diff to improve the error message:

-        require(balance >= amount, "VultMerge: Insufficient balance to eat");
+        require(balance >= amount, "VultMerge: Insufficient balance to merge");
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 1746b7d and 33982f1.

📒 Files selected for processing (14)
  • contracts/core/merge/DynamicEater.sol (1 hunks)
  • contracts/core/merge/VultMerge.sol (1 hunks)
  • contracts/interfaces/IMergeV2.sol (1 hunks)
  • ignition/deployments/chain-8453/artifacts/MergeFactoryModule#MergeFactory.dbg.json (0 hunks)
  • ignition/deployments/chain-8453/artifacts/MergeFactoryModule#MergeFactory.json (0 hunks)
  • ignition/deployments/chain-8453/artifacts/MergeModule#MergeWithMarket.dbg.json (0 hunks)
  • ignition/deployments/chain-8453/artifacts/MergeModule#MergeWithMarket.json (0 hunks)
  • ignition/deployments/chain-8453/artifacts/VultMergeModule#VultMerge.dbg.json (1 hunks)
  • ignition/deployments/chain-8453/artifacts/VultMergeModule#VultMerge.json (1 hunks)
  • ignition/deployments/chain-8453/deployed_addresses.json (1 hunks)
  • ignition/deployments/chain-8453/journal.jsonl (1 hunks)
  • ignition/modules/VultMerge.ts (1 hunks)
  • package.json (1 hunks)
  • test/VultMerge.test.ts (1 hunks)
💤 Files with no reviewable changes (4)
  • ignition/deployments/chain-8453/artifacts/MergeFactoryModule#MergeFactory.dbg.json
  • ignition/deployments/chain-8453/artifacts/MergeFactoryModule#MergeFactory.json
  • ignition/deployments/chain-8453/artifacts/MergeModule#MergeWithMarket.dbg.json
  • ignition/deployments/chain-8453/artifacts/MergeModule#MergeWithMarket.json
✅ Files skipped from review due to trivial changes (1)
  • ignition/deployments/chain-8453/artifacts/VultMergeModule#VultMerge.dbg.json
🧰 Additional context used
🪛 Biome
test/VultMerge.test.ts

[error] 5-5: Don't focus the test.

The 'only' method is often used for debugging or during implementation. It should be removed before deploying to production.
Consider removing 'only' to ensure all tests are executed.
Unsafe fix: Remove focus from test.

(lint/suspicious/noFocusedTests)

🔇 Additional comments (7)
ignition/deployments/chain-8453/deployed_addresses.json (1)

1-3: LGTM! Deployment address updated for VultMerge.

The changes in this file accurately reflect the architectural updates described in the PR objectives and AI summary. The new VultMerge module has been added with a valid Ethereum address.

To ensure the deployment process was executed correctly, please run the following verification script:

This script will help verify that the deployment process was executed correctly, ensuring that the VultMerge module is properly deployed and the old modules are removed.

✅ Verification successful

VultMerge deployment successfully verified.

All verification tests passed, confirming that the VultMerge module is correctly deployed and the old modules have been removed as intended.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the deployment of VultMerge and removal of old modules

# Test 1: Check if VultMerge is properly deployed
echo "Verifying VultMerge deployment:"
rg -q "0x59fA35216Afb2Ab62e254246E13b412656a71831" ignition/deployments/chain-8453/journal.jsonl && echo "VultMerge address found in deployment journal" || echo "VultMerge address not found in deployment journal"

# Test 2: Ensure old modules are no longer present
echo "Verifying removal of old modules:"
! rg -q "ChaosModule#CHAOS" ignition/deployments/chain-8453/journal.jsonl && echo "ChaosModule removed" || echo "ChaosModule still present"
! rg -q "UniAdaptorModule#UniswapV3ViaRouter" ignition/deployments/chain-8453/journal.jsonl && echo "UniAdaptorModule removed" || echo "UniAdaptorModule still present"
! rg -q "MergeModule#MergeWithMarket" ignition/deployments/chain-8453/journal.jsonl && echo "MergeModule removed" || echo "MergeModule still present"
! rg -q "MergeFactoryModule#MergeFactory" ignition/deployments/chain-8453/journal.jsonl && echo "MergeFactoryModule removed" || echo "MergeFactoryModule still present"

# Test 3: Check if VultMerge is properly referenced in the module file
echo "Verifying VultMerge module definition:"
rg -q "VultMerge" ignition/modules/VultMerge.ts && echo "VultMerge found in module definition" || echo "VultMerge not found in module definition"

Length of output: 1115

ignition/modules/VultMerge.ts (1)

1-1: LGTM: Import statement is correct.

The import of buildModule from the Hardhat Ignition package is appropriate for creating the deployment module.

package.json (2)

22-22: LGTM: New deployment script for VultMerge

The addition of the deploy:vult:base script aligns with the PR objectives. It correctly deploys the new VultMerge.ts module on the base network with verification, which is a good practice for contract deployment.


21-22: Update deployment scripts to reflect current project structure

The changes in the package.json file reflect the transition from MergeWithMarket to VultMerge, which aligns with the PR objectives. However, there's an inconsistency:

  1. The old deploy:mergewithmarket script is still present, despite the removal of the corresponding contract.
  2. A new deploy:vult:base script has been correctly added for the new VultMerge contract.

To maintain a clean and accurate project configuration:

  1. Remove the unused deploy:mergewithmarket script.
  2. Ensure all other scripts in the file are still relevant to the current project structure.
  3. Consider adding a comment or documentation explaining the purpose of the VultMerge deployment, especially if it's replacing functionality previously handled by MergeWithMarket.

These changes will help prevent confusion and ensure that the package.json accurately represents the current state of the project.

To ensure all scripts are relevant, you can run:

This will help identify any other potential mismatches between deployment scripts and actual contract files.

✅ Verification successful

Review Comment Adjustment

The MergeWithMarket contract is still present in the codebase, so the deploy:mergewithmarket script remains necessary for deployment. Please ensure the contract is fully removed before deleting its deployment script.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: List all deployment scripts and their corresponding contract files

echo "Deployment scripts:"
grep -E '"deploy:.*"' package.json

echo -e "\nCorresponding contract files:"
fd -e sol -e ts . contracts ignition

Length of output: 3598

ignition/deployments/chain-8453/artifacts/VultMergeModule#VultMerge.json (2)

1-585: Overall structure and content of the VultMerge contract artifact

The file VultMergeModule#VultMerge.json contains the contract ABI, bytecode, and deployed bytecode for the VultMerge contract. This artifact is crucial for deploying and interacting with the contract on chain-8453. Let's review some key aspects:

  1. Constructor (lines 7-25):
    The constructor takes three parameters: _wewe and _vult (both addresses) and _vestingDuration (uint32). This aligns with the PR objectives of introducing a new Vesting struct.

  2. Events (lines 48-122):
    The contract defines several events, including Merged, OwnershipTransferred, Paused, RateChanged, and Unpaused. These events provide important visibility into the contract's state changes.

  3. Key Functions:

    • merge (lines 235-245): Implements the core merging functionality.
    • mergeAndSell (lines 248-268): Combines merging and selling operations.
    • claim (lines 163-167): Allows users to claim tokens, likely related to vesting.
    • setVestingDuration (lines 399-410): Allows updating the vesting duration.
  4. Security Considerations:

    • The contract implements ownership controls (e.g., transferOwnership, renounceOwnership).
    • Pause functionality is present (togglePause, paused), allowing for emergency stops.
  5. Vesting Implementation:

    • The vestings mapping (lines 499-513) stores vesting information, including amount and end, which aligns with the PR objectives.

The contract structure and functionality appear to be consistent with the PR objectives of introducing a Vesting struct and implementing related functionality.


499-513: Vesting struct implementation

The vestings mapping implements the Vesting struct as mentioned in the PR objectives:

"vestings": {
  "inputs": [
    {
      "internalType": "address",
      "name": "",
      "type": "address"
    }
  ],
  "name": "vestings",
  "outputs": [
    {
      "internalType": "uint256",
      "name": "amount",
      "type": "uint256"
    },
    {
      "internalType": "uint256",
      "name": "end",
      "type": "uint256"
    }
  ],
  "stateMutability": "view",
  "type": "function"
}

This implementation correctly includes the amount and end properties as specified in the PR objectives. The mapping allows for efficient lookup of vesting information by address.

ignition/deployments/chain-8453/journal.jsonl (1)

3-7: LGTM

The deployment entries for VultMerge are correctly specified, and the transaction details confirm a successful deployment.

Comment on lines 3 to 4
const WEWE_ADDRESS = "0x6b9bb36519538e0C073894E964E90172E1c0B41F";
const VULT_IOU_ADDRESS = "0x299A57C1f6761b3dB304dc8B18bb4E60A1CF37b6";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider using environment variables for addresses.

While the constants are correctly defined, hardcoding addresses directly in the deployment script may reduce maintainability and flexibility. Consider using environment variables or a configuration file for these values to make the deployment process more adaptable to different environments.

Example refactor:

import dotenv from 'dotenv';
dotenv.config();

const WEWE_ADDRESS = process.env.WEWE_ADDRESS || "0x6b9bb36519538e0C073894E964E90172E1c0B41F";
const VULT_IOU_ADDRESS = process.env.VULT_IOU_ADDRESS || "0x299A57C1f6761b3dB304dc8B18bb4E60a1CF37b6";

Don't forget to add the necessary environment variables to your .env file and update the package.json to include the dotenv package.

@@ -18,7 +18,8 @@
"deploy:bro:base": "npx hardhat ignition deploy ignition/modules/BroMerge.ts --network base --verify",
"deploy:factory": "npx hardhat ignition deploy ignition/modules/MergeFactory.ts --network base --verify",
"deploy:uni": "npx hardhat ignition deploy ignition/modules/UniAdaptor.ts --network base --verify",
"deploy:mergewithmarket": "npx hardhat ignition deploy ignition/modules/MergeWithMarket.ts --network base --verify"
"deploy:mergewithmarket": "npx hardhat ignition deploy ignition/modules/MergeWithMarket.ts --network base --verify",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove unused deployment script

The deploy:mergewithmarket script is no longer needed as the MergeWithMarket contract has been removed from the project. Keeping unused scripts can lead to confusion and potential errors in the future.

Consider removing this line:

- "deploy:mergewithmarket": "npx hardhat ignition deploy ignition/modules/MergeWithMarket.ts --network base --verify",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"deploy:mergewithmarket": "npx hardhat ignition deploy ignition/modules/MergeWithMarket.ts --network base --verify",

Comment on lines 28 to 45
"inputs": [],
"name": "InvalidTokenReceived",
"type": "error"
},
{
"inputs": [],
"name": "MergeLocked",
"type": "error"
},
{
"inputs": [],
"name": "VultToWeweNotAllwed",
"type": "error"
},
{
"inputs": [],
"name": "ZeroAmount",
"type": "error"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Potential issue with error naming

There's a typo in one of the error definitions:

{
  "inputs": [],
  "name": "VultToWeweNotAllwed",
  "type": "error"
}

The error name "VultToWeweNotAllwed" is misspelled. It should be "VultToWeweNotAllowed".

Consider fixing the typo in the error name:

-  "name": "VultToWeweNotAllwed",
+  "name": "VultToWeweNotAllowed",

Comment on lines 399 to 410
{
"inputs": [
{
"internalType": "uint32",
"name": "duration",
"type": "uint32"
}
],
"name": "setVestingDuration",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Vesting duration setter function

The contract includes a function to set the vesting duration:

{
  "inputs": [
    {
      "internalType": "uint32",
      "name": "duration",
      "type": "uint32"
    }
  ],
  "name": "setVestingDuration",
  "outputs": [],
  "stateMutability": "nonpayable",
  "type": "function"
}

This function allows for updating the vesting duration, which is a crucial parameter for the vesting mechanism. However, there are a few considerations:

  1. The function doesn't seem to have any access control. It's recommended to restrict this function to only be callable by the contract owner or a designated admin.
  2. There's no event emitted when the vesting duration is changed. Adding an event would improve transparency and make it easier for off-chain services to track changes.

Consider adding access control and an event to the setVestingDuration function. Here's a suggested implementation:

function setVestingDuration(uint32 duration) external onlyOwner {
    vestingDuration = duration;
    emit VestingDurationChanged(duration);
}

event VestingDurationChanged(uint32 newDuration);

Also, ensure that changing the vesting duration doesn't affect existing vesting schedules, or clearly document the behavior if it does.

Comment on lines 235 to 245
"inputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "merge",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Review of merge and mergeAndSell functions

The contract includes two key functions for merging:

  1. merge function:
{
  "inputs": [
    {
      "internalType": "uint256",
      "name": "amount",
      "type": "uint256"
    }
  ],
  "name": "merge",
  "outputs": [],
  "stateMutability": "nonpayable",
  "type": "function"
}
  1. mergeAndSell function:
{
  "inputs": [
    {
      "internalType": "uint256",
      "name": "amount",
      "type": "uint256"
    },
    {
      "internalType": "contract IAMM",
      "name": "amm",
      "type": "address"
    },
    {
      "internalType": "bytes",
      "name": "extraData",
      "type": "bytes"
    }
  ],
  "name": "mergeAndSell",
  "outputs": [],
  "stateMutability": "nonpayable",
  "type": "function"
}

These functions seem to implement the core functionality of the VultMerge contract. However, there are a few points to consider:

  1. The merge function doesn't provide any return value or emit an event. It might be helpful to emit an event for better transparency and easier off-chain tracking.

  2. The mergeAndSell function interacts with an external AMM contract. This introduces potential security risks if the AMM contract is malicious or compromised.

Consider the following improvements:

  1. Add an event emission to the merge function:
event Merged(address indexed user, uint256 amount);

function merge(uint256 amount) external {
    // existing logic
    emit Merged(msg.sender, amount);
}
  1. For the mergeAndSell function, implement additional safety checks:
function mergeAndSell(uint256 amount, IAMM amm, bytes calldata extraData) external {
    require(isWhitelistedAMM(address(amm)), "AMM not whitelisted");
    // existing logic
}

function isWhitelistedAMM(address amm) public view returns (bool) {
    // Implementation to check if the AMM is whitelisted
}

These changes would enhance the security and transparency of the merging operations.

Also applies to: 248-268

}

// vult in, wewe out
uint256 weweOut = _getRate() * amount * _ratePrecision;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Potential Calculation Error in weweOut

The calculation of weweOut may contain a logic error. Multiplying _getRate(), amount, and _ratePrecision might not yield the correct result due to incorrect handling of precision.

Verify the calculation formula to ensure it accurately computes weweOut. If _getRate() returns a rate scaled by _ratePrecision, consider adjusting the formula:

-            uint256 weweOut = _getRate() * amount * _ratePrecision;
+            uint256 weweOut = (amount * _getRate()) / _ratePrecision;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
uint256 weweOut = _getRate() * amount * _ratePrecision;
uint256 weweOut = (amount * _getRate()) / _ratePrecision;

⚠️ Potential issue

Undefined Variable: _ratePrecision

The variable _ratePrecision used in the calculation of weweOut is not declared in this contract. Ensure that _ratePrecision is defined or imported appropriately to avoid compilation errors.

If _ratePrecision is intended to represent a precision factor for rate calculations, define it in the contract. For example:

+    uint256 private constant _ratePrecision = 1e18; // Adjust the value as needed

Committable suggestion was skipped due to low confidence.

Comment on lines 64 to 66
if (lockedStatus != LockedStatus.TwoWay) {
revert VultToWeweNotAllwed();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Correction Needed: Typo in Revert Function Name

The error function VultToWeweNotAllwed() contains a typo. It should be VultToWeweNotAllowed() to match the intended error name and improve code clarity.

Apply this diff to correct the typo:

-            revert VultToWeweNotAllwed();
+            revert VultToWeweNotAllowed();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (lockedStatus != LockedStatus.TwoWay) {
revert VultToWeweNotAllwed();
}
if (lockedStatus != LockedStatus.TwoWay) {
revert VultToWeweNotAllowed();
}

_currentHeld -= amount;
vestings[msg.sender].amount = 0;

IERC20(wewe).transfer(msg.sender, amount);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Use safeTransfer Instead of transfer for ERC20 Transfers

In line 119, IERC20(wewe).transfer(msg.sender, amount); uses the standard transfer method. To ensure safer token transfers and handle non-standard ERC20 implementations, it's recommended to use safeTransfer from the SafeERC20 library.

Apply this diff to use safeTransfer:

-            IERC20(wewe).transfer(msg.sender, amount);
+            IERC20(wewe).safeTransfer(msg.sender, amount);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
IERC20(wewe).transfer(msg.sender, amount);
IERC20(wewe).safeTransfer(msg.sender, amount);
```
Note: To use `safeTransfer`, you'll need to import and use the `SafeERC20` library from OpenZeppelin. Make sure to add the following import at the beginning of your contract file if it's not already present:
```solidity
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
```
And add the following using statement in your contract:
```solidity
using SafeERC20 for IERC20;

import { expect } from "chai";
import { ethers } from "hardhat";

describe.only("Vult Merge Contract", function () {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove '.only' to run all test suites

The .only method is used to focus on a single test suite during debugging. It should be removed to ensure that all tests are executed.

Apply this diff to remove .only:

-describe.only("Vult Merge Contract", function () {
+describe("Vult Merge Contract", function () {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
describe.only("Vult Merge Contract", function () {
describe("Vult Merge Contract", function () {
🧰 Tools
🪛 Biome

[error] 5-5: Don't focus the test.

The 'only' method is often used for debugging or during implementation. It should be removed before deploying to production.
Consider removing 'only' to ensure all tests are executed.
Unsafe fix: Remove focus from test.

(lint/suspicious/noFocusedTests)

Comment on lines 82 to 85
await expect(vult.approveAndCall(merge, 100, "0x")).to.be.revertedWithCustomError(merge, "VultToWeweNotAllwed");
await merge.setLockedStatus(1);
await expect(vult.approveAndCall(merge, 100, "0x")).to.be.revertedWithCustomError(merge, "VultToWeweNotAllwed");
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

⚠️ Potential issue

Multiple Occurrences of Misspelled Error 'VultToWeweNotAllwed' Found

The misspelled error name 'VultToWeweNotAllwed' appears in several files. Please correct all instances to 'VultToWeweNotAllowed':

  • test/VultMerge.test.ts
  • test/Merge.test.ts
  • contracts/interfaces/IMerge.sol
  • contracts/core/merge/VultMerge.sol
  • contracts/core/Merge.sol
  • ignition/deployments/chain-8453/build-info/4a8496326ea78d00d21b66cae1dc11a6.json
🔗 Analysis chain

Correct the misspelled error name 'VultToWeweNotAllwed'

The custom error name 'VultToWeweNotAllwed' appears to be misspelled. It should likely be 'VultToWeweNotAllowed'.

Apply this diff to correct the spelling in the test:

await expect(vult.approveAndCall(merge, 100, "0x")).to.be.revertedWithCustomError(merge, "VultToWeweNotAllwed");
-await expect(vult.approveAndCall(merge, 100, "0x")).to.be.revertedWithCustomError(merge, "VultToWeweNotAllwed");
+await expect(vult.approveAndCall(merge, 100, "0x")).to.be.revertedWithCustomError(merge, "VultToWeweNotAllowed");

Run the following script to check for other occurrences of the misspelled error name in the codebase:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find all occurrences of 'VultToWeweNotAllwed' in the codebase.

# Expectation: The misspelled error name should not appear elsewhere.
rg 'VultToWeweNotAllwed'

Length of output: 20979

@bitcoinbrisbane bitcoinbrisbane marked this pull request as ready for review October 23, 2024 00:04
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

🧹 Outside diff range and nitpick comments (5)
test/VultMerge.test.ts (4)

19-22: Define constants for magic numbers.

Consider extracting magic numbers into named constants at the top of the file for better maintainability and readability.

+const MAX_SUPPLY = 1000n;
+const VESTING_PERIOD = 60;
+
 // Max supply of 1000 tokens to eat
-const maxSupply = 1000n // ethers.parseEther("1000");
-const vestingPeriod = 60;
+const maxSupply = MAX_SUPPLY;
+const vestingPeriod = VESTING_PERIOD;

38-46: Add more comprehensive initial setup test cases.

The initial setup tests should be expanded to include:

  1. Validation of vestingPeriod and maxSupply
  2. Negative test cases for invalid constructor parameters
  3. Edge cases for zero addresses

Example test cases to add:

it("Should set the correct vesting period and max supply", async function () {
  const { merge } = await loadFixture(deployFixture);
  expect(await merge.vestingPeriod()).to.equal(VESTING_PERIOD);
  expect(await merge.maxSupply()).to.equal(MAX_SUPPLY);
});

it("Should revert with zero addresses", async function () {
  const Merge = await ethers.getContractFactory("VultMerge");
  await expect(
    Merge.deploy(ethers.ZeroAddress, ethers.ZeroAddress, VESTING_PERIOD, MAX_SUPPLY)
  ).to.be.revertedWith("Invalid address");
});

54-55: Define constant for expected rate value.

The magic number 12000000n should be defined as a constant with a clear name explaining its significance.

+const INITIAL_RATE = 12000000n; // 120% represented as basis points
 let rate = await merge.getRate();
-expect(rate).to.be.eq(12000000n); // Starting rate should be 120%
+expect(rate).to.be.eq(INITIAL_RATE);

48-80: Expand test coverage for dynamic rates.

The dynamic rates test suite should be expanded to include:

  1. Edge cases for minimum and maximum rates
  2. Validation of vesting end time calculation
  3. Multiple merge operations and their impact on rates

Example additional test case:

it("Should respect minimum rate boundary", async () => {
  const { merge, otherAccount } = await loadFixture(deployFixture);
  
  // Perform multiple merges to reduce rate
  for(let i = 0; i < 10; i++) {
    await merge.connect(otherAccount).merge(100n);
  }
  
  const rate = await merge.getRate();
  expect(rate).to.be.gte(await merge.MIN_RATE());
});
contracts/core/merge/DynamicEater.sol (1)

11-11: Remove unnecessary import of console.sol

The console.sol import is typically used for debugging during development and should not be included in production code.

Apply this diff to remove the import:

-import "hardhat/console.sol";
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 33982f1 and 71a2e4a.

📒 Files selected for processing (4)
  • contracts/core/merge/DynamicEater.sol (1 hunks)
  • contracts/core/merge/VultMerge.sol (1 hunks)
  • package.json (1 hunks)
  • test/VultMerge.test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • package.json
🧰 Additional context used
🪛 Biome
test/VultMerge.test.ts

[error] 5-5: Don't focus the test.

The 'only' method is often used for debugging or during implementation. It should be removed before deploying to production.
Consider removing 'only' to ensure all tests are executed.
Unsafe fix: Remove focus from test.

(lint/suspicious/noFocusedTests)

🔇 Additional comments (2)
test/VultMerge.test.ts (1)

12-16: Add validation for empty token names and symbols.

The test deploys tokens with empty strings for name and symbol. Consider adding test cases to verify behavior with valid token metadata.

contracts/core/merge/DynamicEater.sol (1)

88-89: Verify the calculation of weweToTransfer

In the _merge function, weweToTransfer is calculated using _totalVested + amount:

uint256 weweToTransfer = getTotalWeWe(_totalVested + amount);

Please verify whether the intention is to calculate based on amount or the cumulative _totalVested + amount. Using _totalVested + amount might not reflect the correct amount of Wewe tokens to transfer for the current merge.

Comment on lines 76 to 78
const vested = await merge.vestings(otherAccount.address);

expect(vested.amount).to.be.eq(50n);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Based on the test file content, I can now provide a final response:

The review comment is valid - end time validation is missing

The test only validates the vested amount (vested.amount) but doesn't check the end timestamp, even though a vestingPeriod of 60 is set in the fixture. The test should verify that the vesting end time is correctly calculated by adding the vesting period to the current timestamp.

Key points:

  • The test fixture sets vestingPeriod = 60
  • The vested struct is retrieved but only amount is verified
  • No assertions are made for the vesting end time
🔗 Analysis chain

Verify vesting end time calculation.

The test only verifies the vested amount but should also validate the end timestamp in the Vesting struct.


Let me try a different search pattern to gather more context about the vesting tests and structure.


Let me try one more search to find the complete test context and vesting implementation.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if there are any tests validating vesting end time
rg -A 5 'vesting.*end' test/

Length of output: 30


Script:

#!/bin/bash
# First, let's look at the vesting struct definition and usage
ast-grep --pattern 'struct Vesting {
  $$$
}'

# Also search for any vesting-related test assertions
rg -A 3 'expect.*vesting' test/

Length of output: 525


Script:

#!/bin/bash
# Look for the complete test file content
cat test/VultMerge.test.ts

# Search for vestings mapping or function definition
ast-grep --pattern 'function vestings($$$) {
  $$$
}'

Length of output: 2804

Comment on lines 86 to 87
require(balance >= amount, "VultMerge: Insufficient balance to eat");

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Update error messages to match the function's action

The error message "Insufficient balance to eat" does not align with the action performed by the functions mergeAndSell and merge, which is merging tokens. For clarity and consistency, consider updating the error message to "Insufficient balance to merge".

Apply this diff to correct the error messages:

In the mergeAndSell function:

-    require(balance >= amount, "VultMerge: Insufficient balance to eat");
+    require(balance >= amount, "VultMerge: Insufficient balance to merge");

In the merge function:

-    require(balance >= amount, "VultMerge: Insufficient balance to eat");
+    require(balance >= amount, "VultMerge: Insufficient balance to merge");

Also applies to: 100-101

_merge(amount, _token, msg.sender);

// Approve the AMM to use the tokens now in this contract
IERC20(_token).approve(address(amm), amount);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Use safeApprove instead of approve for ERC20 approvals

To ensure safer interactions with ERC20 tokens and handle non-standard implementations, it's recommended to use safeApprove from the SafeERC20 library instead of the standard approve method.

Apply this diff to update the approval method:

-    IERC20(_token).approve(address(amm), amount);
+    IERC20(_token).safeApprove(address(amm), amount);

Committable suggestion was skipped due to low confidence.

abstract contract DynamicEater is IWeweReceiver, ReentrancyGuard, Pausable, Ownable {
int256 internal constant RATE_PRECISSION = 100_000;
address internal _token;
address public wewe;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Initialize the wewe token address

The wewe token address is declared but not initialized in the contract. This could lead to unexpected behavior if the address remains unset.

Consider adding a setter function to initialize the wewe address:

function setWeweAddress(address _wewe) external onlyOwner {
    require(_wewe != address(0), "DynamicEater: Invalid WEWE address");
    wewe = _wewe;
}

Ensure that this function is called with the correct address before the contract is used.


abstract contract DynamicEater is IWeweReceiver, ReentrancyGuard, Pausable, Ownable {
int256 internal constant RATE_PRECISSION = 100_000;
address internal _token;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Initialize the _token address

Similarly, the _token address is declared but not set. This is required for the _merge function to operate correctly.

Consider adding a setter function for _token:

function setTokenAddress(address token) external onlyOwner {
    require(token != address(0), "DynamicEater: Invalid token address");
    _token = token;
}

Comment on lines 73 to 75
function _getRate(uint256 x1, uint256 x2) internal view returns (uint256) {
// Slope is a constant, from max rate at 0 tokens, to min rate at max supply
int256 dxdy = (minRate - maxRate) / int256(maxSupply);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Prevent division by zero in _getRate function

The _getRate function divides by int256(maxSupply), which could result in a division by zero error if maxSupply is zero.

Add a check to ensure maxSupply is not zero:

function _getRate(uint256 x1, uint256 x2) internal view returns (uint256) {
+    require(maxSupply != 0, "DynamicEater: maxSupply cannot be zero");
    // Slope is a constant, from max rate at 0 tokens, to min rate at max supply
    int256 dxdy = (minRate - maxRate) / int256(maxSupply);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function _getRate(uint256 x1, uint256 x2) internal view returns (uint256) {
// Slope is a constant, from max rate at 0 tokens, to min rate at max supply
int256 dxdy = (minRate - maxRate) / int256(maxSupply);
function _getRate(uint256 x1, uint256 x2) internal view returns (uint256) {
require(maxSupply != 0, "DynamicEater: maxSupply cannot be zero");
// Slope is a constant, from max rate at 0 tokens, to min rate at max supply
int256 dxdy = (minRate - maxRate) / int256(maxSupply);

Comment on lines 104 to 107
vestings[msg.sender] = Vesting({
amount: weweToTransfer + vestedAmount,
end: block.timestamp + vestingDuration * 1 minutes
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Ensure consistent use of from and msg.sender

When updating the vesting records, you are assigning the new vesting to vestings[msg.sender], while the from address is used for other operations. This inconsistency can lead to incorrect vesting assignments.

Apply this diff to use the from address consistently:

uint256 vestedAmount = vestings[from].amount;
-vestings[msg.sender] = Vesting({
+vestings[from] = Vesting({
    amount: weweToTransfer + vestedAmount,
    end: block.timestamp + vestingDuration * 1 minutes
});

Committable suggestion was skipped due to low confidence.

Comment on lines +122 to +128
/// @notice Wewe token approveAndCall function
function receiveApproval(
address from,
uint256 amount,
address,
bytes calldata
) external nonReentrant whenNotPaused {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Restrict receiveApproval function to the wewe token contract

The receiveApproval function should only be callable by the wewe token contract to prevent unauthorized access.

Add a modifier to restrict access:

+modifier onlyWeweToken() {
+    require(msg.sender == wewe, "DynamicEater: Unauthorized sender");
+    _;
+}

/// @notice Wewe token approveAndCall function
function receiveApproval(
    address from,
    uint256 amount,
    address,
    bytes calldata
) external nonReentrant whenNotPaused onlyWeweToken {
    // After wewe approve and call, it will call this function
    require(_token != address(0), "Eater: Token address not set");

Committable suggestion was skipped due to low confidence.

Comment on lines +145 to +146
IERC20(wewe).transferFrom(msg.sender, address(this), amount);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Use SafeERC20 for token transfers

Direct use of transferFrom can lead to issues if the token does not return a boolean value. It's safer to use OpenZeppelin's SafeERC20 library to handle token transfers.

Update the code to use SafeERC20:

+import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
+
+using SafeERC20 for IERC20;
 
 function _deposit(uint256 amount) internal {
-    IERC20(wewe).transferFrom(msg.sender, address(this), amount);
+    IERC20(wewe).safeTransferFrom(msg.sender, address(this), amount);
}

Committable suggestion was skipped due to low confidence.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Outside diff range and nitpick comments (6)
ignition/modules/DynamicEaterModule.ts (3)

4-5: Consider making addresses configurable based on deployment environment.

Hardcoding contract addresses directly in the module can cause issues when deploying to different networks (mainnet, testnet, local). Consider:

  1. Moving these addresses to a configuration file
  2. Using environment variables
  3. Adding network-specific address mapping

Example configuration approach:

// config/addresses.ts
export const ADDRESSES = {
  mainnet: {
    WEWE: "0x6b9bb36519538e0C073894E964E90172E1c0B41F",
    VULT_IOU: "0x299A57C1f6761b3dB304dc8B18bb4E60a1CF37b6"
  },
  testnet: {
    // testnet addresses
  }
};

8-9: Document the vesting period unit and consider using a more explicit constant.

The vestingPeriod value of 60 lacks context about its time unit (seconds, minutes, days?). This could lead to confusion or incorrect vesting durations.

Consider using a more explicit constant:

const VESTING_PERIOD_SECONDS = 60; // or minutes/days as appropriate
const MAX_SUPPLY_TOKENS = "100";

1-13: Consider adding deployment verification and documentation.

The module would benefit from:

  1. Post-deployment verification steps
  2. Documentation about the expected behavior
  3. Network-specific configurations

Consider extending the module with verification:

export default buildModule("DynamicEaterModule", async (m) => {
  // ... existing code ...

  // Add post-deployment verification
  m.afterDeploy(async (_, contracts) => {
    const dynamicEater = contracts.merge;
    const vestingPeriodValue = await dynamicEater.vestingPeriod();
    const maxSupplyValue = await dynamicEater.maxSupply();
    
    console.log("Deployment verified:");
    console.log(`Vesting Period: ${vestingPeriodValue}`);
    console.log(`Max Supply: ${maxSupplyValue}`);
  });

  return { merge };
});
test/DynamicEater.test.ts (3)

12-14: Add documentation for empty constructor parameters.

The Vult contract is deployed with empty strings for name and symbol parameters. This should be documented or use meaningful test values.

-		const vult = await Vult.deploy("", "");
-		const mockVult = await Vult.deploy("", "");
+		// Deploy Vult tokens with test name/symbol
+		const vult = await Vult.deploy("Test Vult", "tVULT");
+		const mockVult = await Vult.deploy("Mock Vult", "mVULT");

18-22: Extract magic numbers into named constants.

Magic numbers should be defined as constants at the top of the test file for better maintainability.

// Add at the top of the file
const TEST_MAX_SUPPLY = 1000n;
const TEST_VESTING_PERIOD = 60;
const TEST_APPROVAL_AMOUNT = 10000n;

54-55: Document the rate calculation.

The initial rate value of 12000000n (120%) should be documented and possibly defined as a constant.

-			expect(rate).to.be.eq(12000000n); // Starting rate should be 120%
+			const INITIAL_RATE = 12000000n; // 120% represented as 12000000/10000000
+			expect(rate).to.be.eq(INITIAL_RATE);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 71a2e4a and af6d113.

📒 Files selected for processing (7)
  • contracts/core/merge/DynamicEater.sol (1 hunks)
  • ignition/deployments/chain-8453/artifacts/VultMergeModule#VultMerge.dbg.json (1 hunks)
  • ignition/deployments/chain-8453/artifacts/VultMergeModule#VultMerge.json (1 hunks)
  • ignition/deployments/chain-8453/deployed_addresses.json (1 hunks)
  • ignition/deployments/chain-8453/journal.jsonl (1 hunks)
  • ignition/modules/DynamicEaterModule.ts (1 hunks)
  • test/DynamicEater.test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • ignition/deployments/chain-8453/artifacts/VultMergeModule#VultMerge.dbg.json
  • ignition/deployments/chain-8453/deployed_addresses.json
🧰 Additional context used
🔇 Additional comments (11)
ignition/modules/DynamicEaterModule.ts (1)

10-10: Verify contract parameters and add input validation.

The contract deployment parameters should be validated before passing to the constructor to prevent deployment with incorrect values.

Consider adding parameter validation:

if (vestingPeriod <= 0) {
  throw new Error("Vesting period must be positive");
}
if (maxSupply.lte(0)) {
  throw new Error("Max supply must be positive");
}

Let's verify the contract's existence and interface:

test/DynamicEater.test.ts (2)

1-4: LGTM: Appropriate test dependencies imported.

The imports include all necessary testing utilities from Hardhat and Chai.


27-33: Verify token transfer and approval success.

Token transfers and approvals should be verified to ensure the test setup is correct.

-		await vult.transfer(otherAccount, 1000n);
+		await expect(vult.transfer(otherAccount, 1000n))
+			.to.emit(vult, 'Transfer')
+			.withArgs(owner.address, otherAccount.address, 1000n);
+		expect(await vult.balanceOf(otherAccount.address)).to.equal(1000n);

-		await wewe.approve(mergeAddress, 10000n);
-		await vult.connect(otherAccount).approve(mergeAddress, 10000n);
+		await expect(wewe.approve(mergeAddress, 10000n))
+			.to.emit(wewe, 'Approval')
+			.withArgs(owner.address, mergeAddress, 10000n);
+		await expect(vult.connect(otherAccount).approve(mergeAddress, 10000n))
+			.to.emit(vult, 'Approval')
+			.withArgs(otherAccount.address, mergeAddress, 10000n);
ignition/deployments/chain-8453/journal.jsonl (4)

3-3: Review the vesting duration parameter.

The vesting duration is set to 60 seconds, which is extremely short for a production environment. Typical vesting periods are measured in months or years.

Consider adjusting the vesting duration to a more realistic timeframe. For example:

  • 180 days (15,552,000 seconds)
  • 365 days (31,536,000 seconds)
  • 4 years (126,144,000 seconds)

5-6: Transaction fees are optimized for Base chain.

The gas configuration is appropriate:

  • maxFeePerGas: 7,020,676 wei
  • maxPriorityFeePerGas: 1,000,000 wei
    These values are reasonable for Base chain.

6-7: Deployment completed successfully.

The contract was successfully deployed to 0xA12E4BbEC5565c7e8470fd5B0A42301cc8fF50C6 with:

  • Block number: 21426392
  • Block hash: 0x1a9cdd6bc23786170150a47c9fe68f5cf3fbe1c242e7badfc0321620f1674399
  • Ownership transferred to deployer: 0x0625db97368df1805314e68d0e63e5eb154b9ae6

3-7: Verify contract addresses and initial token amount.

Let's verify the constructor arguments:

✅ Verification successful

Let me gather more information about these addresses by checking for any configuration files or deployment records that might reference them.


Let me search for the contract implementation to understand the role of these addresses and verify the token amount.


Let me check the README and deployment artifacts to understand the purpose of these addresses.


Let me check the build info to understand the constructor parameters and contract purpose.


Based on the gathered information, I can now provide a final verification response:

Constructor arguments and token amounts are correctly configured

The deployment is correctly configured with:

  • WeWe token (0x6b9bb36519538e0C073894E964E90172E1c0B41F) documented in README.md as deployed on Base
  • VULT IOU token (0x299A57C1f6761b3dB304dc8B18bb4E60A1CF37b6) referenced in DynamicEaterModule
  • Vesting duration of 60 blocks
  • Max supply of 100 tokens (100000000000000000000 in wei, 18 decimals)

The deployment was successful with the contract deployed at 0xA12E4BbEC5565c7e8470fd5B0A42301cc8fF50C6.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the token addresses and their roles

# Test 1: Check if the addresses are valid contracts
echo "Verifying contract addresses..."
for addr in "0x6b9bb36519538e0C073894E964E90172E1c0B41F" "0x299A57C1f6761b3dB304dc8B18bb4E60A1CF37b6"; do
  # Search for contract verification status or interface implementations
  rg -l "contract.*$addr" || echo "Warning: No contract found for $addr"
done

# Test 2: Check token amount format
echo "Verifying token amount..."
# 100000000000000000000 should be 100 tokens with 18 decimals
if [[ "100000000000000000000" != "100"+"0"*18 ]]; then
  echo "Warning: Token amount format might be incorrect"
fi

Length of output: 932


Script:

#!/bin/bash
# Search for address references in configuration files and deployment records
echo "Searching for address references in configs and deployments..."

# Look for files containing these addresses
rg -A 5 -B 5 "0x6b9bb36519538e0C073894E964E90172E1c0B41F|0x299A57C1f6761b3dB304dc8B18bb4E60A1CF37b6" --type yaml --type json --type jsonl

# Check for any contract deployment or verification records
echo -e "\nChecking deployment records..."
fd -e json -e yaml -e jsonl . deployments/ -x grep -l "0x6b9bb36519538e0C073894E964E90172E1c0B41F\|0x299A57C1f6761b3dB304dc8B18bb4E60A1CF37b6" {} \;

# Look for any contract interfaces or related code
echo -e "\nSearching for contract interfaces..."
rg "interface.*(?:VultMerge|Merge)" --type solidity

Length of output: 15596


Script:

#!/bin/bash
# Search for VultMerge contract implementation
echo "Searching for VultMerge contract..."
fd -e sol . -x cat {} \; | grep -A 20 "contract VultMerge"

# Look for any configuration or documentation about these addresses
echo -e "\nSearching for address documentation..."
rg -i "0x6b9bb36519538e0C073894E964E90172E1c0B41F|0x299A57C1f6761b3dB304dc8B18bb4E60A1CF37b6" --type md --type txt

# Check for any deployment scripts or configurations
echo -e "\nSearching deployment configurations..."
fd -e ts -e js -e json . -x grep -l "VultMerge" {} \;

Length of output: 806


Script:

#!/bin/bash
# Check README content around the WeWe address
echo "Checking README context..."
rg -B 2 -A 2 "0x6b9bb36519538e0C073894E964E90172E1c0B41F" README.md

# Check deployed addresses configuration
echo -e "\nChecking deployed addresses..."
cat ./ignition/deployments/chain-8453/deployed_addresses.json

# Check VultMerge artifact for constructor documentation
echo -e "\nChecking VultMerge artifact..."
cat ./ignition/deployments/chain-8453/artifacts/VultMergeModule#VultMerge.json | grep -A 10 "constructor"

Length of output: 1003


Script:

#!/bin/bash
# Check build info for constructor and contract details
echo "Checking build info..."
cat ./ignition/deployments/chain-8453/build-info/dce1aa99dc63d048e261276b9113459d.json | jq -r '.output.contracts[] | select(. != null) | .[] | select(.abi != null) | .abi[] | select(.type == "constructor")'

# Look for any comments or documentation in the contract
echo -e "\nSearching for contract documentation..."
cat ./ignition/deployments/chain-8453/build-info/dce1aa99dc63d048e261276b9113459d.json | jq -r '.sources[] | select(. != null) | .'

# Check if the second address is documented
echo -e "\nChecking second address documentation..."
rg -B 2 -A 2 "0x299A57C1f6761b3dB304dc8B18bb4E60A1CF37b6" .

Length of output: 15468

ignition/deployments/chain-8453/artifacts/VultMergeModule#VultMerge.json (4)

1-31: LGTM: Constructor parameters are well-designed.

The constructor parameters provide all necessary configuration for a vesting contract:

  • Token addresses (_wewe, _vult)
  • Vesting duration (_vestingDuration)
  • Maximum supply limit (_maxSupply)

32-51: LGTM: Error definitions are appropriate.

The custom errors provide clear failure cases for the contract operations.


52-128: LGTM: Events provide good visibility into contract state changes.

The events are well-designed with:

  • Appropriate use of indexed parameters for efficient filtering
  • Coverage of all important state changes
  • Standard OpenZeppelin events for common functionality

464-468: ⚠️ Potential issue

Add access control to setVirtualWeweBalance function.

The function can modify critical balance state but lacks access control modifiers.

Add the onlyOwner modifier to restrict access:

-  "stateMutability": "nonpayable",
+  "stateMutability": "nonpayable",
+  "modifiers": [{"name": "onlyOwner"}],

Likely invalid or redundant comment.

Comment on lines 38 to 46
describe("Initial Setup", function () {
it("Should set the correct initial values", async function () {
const { merge, vult, wewe } = await loadFixture(deployFixture);
expect(await merge.wewe()).to.equal(await wewe.getAddress());
expect(await merge.getToken()).to.equal(await vult.getAddress());
expect(await merge.weweBalance()).to.equal(0);
expect(await merge.lockedStatus()).to.equal(0);
});
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add comprehensive initial state validation tests.

The initial setup tests should validate all important contract parameters and include negative test cases.

Add these test cases:

it("Should set the correct vesting period", async function () {
  const { merge } = await loadFixture(deployFixture);
  expect(await merge.vestingPeriod()).to.equal(vestingPeriod);
});

it("Should set the correct max supply", async function () {
  const { merge } = await loadFixture(deployFixture);
  expect(await merge.maxSupply()).to.equal(maxSupply);
});

it("Should revert when initialized with zero vesting period", async function () {
  const Merge = await ethers.getContractFactory("DynamicEater");
  await expect(
    Merge.deploy(wewe.address, vult.address, 0, maxSupply)
  ).to.be.revertedWith("Invalid vesting period");
});

Comment on lines 48 to 80
describe("Vult merge with dynamic rates", () => {
it("Should decrease deposit rates", async () => {
const { merge, vult, wewe, otherAccount } = await loadFixture(deployFixture);

const mergeAddress = await merge.getAddress();

let rate = await merge.getRate();
expect(rate).to.be.eq(12000000n); // Starting rate should be 120%

// Deposit 1200 wewe to setup the merge
await merge.deposit(1000n);

expect(await wewe.balanceOf(mergeAddress)).to.be.eq(1000n);

// Other account should have 1000 vult
expect(await vult.balanceOf(otherAccount.address)).to.be.eq(1000n);

let totalVested = await merge.totalVested();
expect(totalVested).to.be.eq(0);

const slope = await merge.slope();
expect(slope).to.be.eq(-70);

const reward = await merge.getTotalWeWe(100n);
expect(reward).to.be.eq(50n);

// Merge 100 vult to wewe
await merge.connect(otherAccount).merge(100n);
const vested = await merge.vestings(otherAccount.address);

expect(vested.amount).to.be.eq(50n);
});
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add edge cases for dynamic rate testing.

The test suite should include additional scenarios to thoroughly test the dynamic rate mechanism.

Add these test cases:

it("Should handle minimum rate correctly", async () => {
  const { merge, otherAccount } = await loadFixture(deployFixture);
  
  // Perform multiple merges to reach minimum rate
  for(let i = 0; i < 10; i++) {
    await merge.connect(otherAccount).merge(100n);
  }
  
  const rate = await merge.getRate();
  expect(rate).to.be.gte(await merge.MIN_RATE());
});

it("Should maintain correct state after multiple merges", async () => {
  const { merge, vult, wewe, otherAccount } = await loadFixture(deployFixture);
  
  const initialBalance = await vult.balanceOf(otherAccount.address);
  const mergeAmount = 50n;
  
  // Perform multiple merges
  await merge.connect(otherAccount).merge(mergeAmount);
  await merge.connect(otherAccount).merge(mergeAmount);
  
  // Verify final state
  expect(await vult.balanceOf(otherAccount.address)).to.eq(initialBalance - (mergeAmount * 2n));
  const vested = await merge.vestings(otherAccount.address);
  expect(vested.amount).to.be.gt(0);
});

Comment on lines 75 to 78
await merge.connect(otherAccount).merge(100n);
const vested = await merge.vestings(otherAccount.address);

expect(vested.amount).to.be.eq(50n);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Validate complete vesting struct properties.

The test only validates the vesting amount but should also check the end timestamp.

 			await merge.connect(otherAccount).merge(100n);
 			const vested = await merge.vestings(otherAccount.address);
 
-			expect(vested.amount).to.be.eq(50n);
+			expect(vested.amount).to.be.eq(50n);
+			const blockTimestamp = (await ethers.provider.getBlock('latest')).timestamp;
+			expect(vested.end).to.be.eq(blockTimestamp + vestingPeriod);

Committable suggestion was skipped due to low confidence.

Comment on lines +48 to +50
function setTreasury(address _treasury) external onlyOwner {
treasury = _treasury;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add input validation for setTreasury function

To prevent setting the treasury address to the zero address, consider adding a check to validate the input.

Apply this diff to add input validation:

 function setTreasury(address _treasury) external onlyOwner {
+    require(_treasury != address(0), "DynamicEater: Invalid treasury address");
     treasury = _treasury;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function setTreasury(address _treasury) external onlyOwner {
treasury = _treasury;
}
function setTreasury(address _treasury) external onlyOwner {
require(_treasury != address(0), "DynamicEater: Invalid treasury address");
treasury = _treasury;
}

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Outside diff range and nitpick comments (9)
test/DynamicEater.test.ts (4)

38-63: Remove or implement the commented test case.

The commented test case contains valuable test scenarios. Either implement it or remove it to maintain code cleanliness.

If you'd like, I can help implement this test case with proper assertions and vesting end time validation as suggested in the past review comments.


77-79: Document the reasoning behind approximate assertions.

The test uses approximately with different margins (100 for both cases). Add comments explaining why these specific margins are acceptable for the scalar calculations.

 scalar = await merge.getScalar(10000);
+// Allow for small variations due to rounding in rate calculations
 expect(scalar).to.be.approximately(11900, 100);

 scalar = await merge.getScalar(500000);
+// Larger amounts may have wider variations due to cumulative rounding
 expect(scalar).to.be.approximately(6200, 100);

Also applies to: 86-88


80-84: Clean up debugging artifacts.

Remove commented-out expectations and console.log statements used for debugging.

-			// reward = await merge.getTotalWeWe(30000);
-			// expect(reward).to.be.eq(117);
-
-			// reward = await merge.getTotalWeWe(100000);
-			// expect(reward).to.be.eq(109);

-			console.log("wewe", weweRecieved.toString());

Also applies to: 92-93


37-94: Consider expanding test coverage for edge cases.

While the current test covers basic scenarios, consider adding these test cases:

  1. Maximum supply reached scenario
  2. Multiple deposits affecting rates
  3. Error cases (invalid amounts, paused contract)
  4. Boundary conditions for scalar calculations

This will ensure the contract behaves correctly under all conditions.

Would you like me to help implement these additional test cases?

🧰 Tools
🪛 Biome

[error] 65-65: Don't focus the test.

The 'only' method is often used for debugging or during implementation. It should be removed before deploying to production.
Consider removing 'only' to ensure all tests are executed.
Unsafe fix: Remove focus from test.

(lint/suspicious/noFocusedTests)

contracts/core/merge/DynamicEater.sol (4)

11-14: Add documentation for the Vesting struct.

Consider adding NatSpec documentation to explain the purpose and units of each field in the Vesting struct.

+/// @notice Struct to track vested token amounts and their release time
 struct Vesting {
+    /// @notice Amount of tokens vested
     uint256 amount;
+    /// @notice Timestamp when the vesting period ends (in seconds since Unix epoch)
     uint256 end;
 }

26-29: Improve documentation for rate constants and ratio.

The rate constants and ratio need clearer documentation:

  1. The rate constants use scaled values but the scaling factor isn't clearly documented
  2. The ratio is hardcoded without explanation
-    int256 public constant maxRate = 12 * 10 ** 4; // 120% or 1.2
-    int256 public constant minRate = 5 * 10 ** 4; // 50% or 0.5
-    uint32 public vestingDuration;
-    uint256 public ratio = 131; // scaled from 1.31
+    /// @notice Maximum rate scaled by RATE_PRECISION (120% = 1.2)
+    int256 public constant maxRate = 12 * 10 ** 4;
+    /// @notice Minimum rate scaled by RATE_PRECISION (50% = 0.5)
+    int256 public constant minRate = 5 * 10 ** 4;
+    /// @notice Duration of the vesting period in minutes
+    uint32 public vestingDuration;
+    /// @notice Conversion ratio scaled by 100 (1.31 = 131)
+    uint256 public constant RATIO = 131;

33-55: Add NatSpec documentation for view functions.

The view functions lack proper documentation explaining their purpose and return values.

+    /// @notice Check if an account can claim their vested tokens
+    /// @param account Address to check
+    /// @return bool True if the account's vesting period has ended
     function canClaim(address account) external view returns (bool) {
         return vestings[account].end <= block.timestamp;
     }

+    /// @notice Get the amount of tokens vested for an account
+    /// @param account Address to check
+    /// @return uint256 Amount of tokens vested
     function balanceOf(address account) external view returns (uint256) {
         return vestings[account].amount;
     }

240-242: Add events for important state changes.

Consider adding events for important state changes to improve contract transparency and off-chain tracking:

     event Merged(uint256 amount, address indexed account);
     event RateChanged(uint256 newRate);
+    event MaxSupplyUpdated(uint256 newMaxSupply);
+    event VestingDurationUpdated(uint32 newDuration);
+    event TreasuryUpdated(address newTreasury);
ignition/deployments/chain-8453/journal.jsonl (1)

5-5: Review gas configuration.

The transaction uses:

  • maxFeePerGas: 7,933,270 wei
  • maxPriorityFeePerGas: 1,000,000 wei

Consider implementing dynamic gas pricing for more cost-effective deployments.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between af6d113 and ef1cadb.

📒 Files selected for processing (8)
  • contracts/core/merge/DynamicEater.sol (1 hunks)
  • ignition/deployments/chain-8453/artifacts/DynamicEaterModule#DynamicEater.dbg.json (1 hunks)
  • ignition/deployments/chain-8453/artifacts/DynamicEaterModule#DynamicEater.json (1 hunks)
  • ignition/deployments/chain-8453/deployed_addresses.json (1 hunks)
  • ignition/deployments/chain-8453/journal.jsonl (1 hunks)
  • ignition/modules/DynamicEaterModule.ts (1 hunks)
  • package.json (1 hunks)
  • test/DynamicEater.test.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • ignition/deployments/chain-8453/artifacts/DynamicEaterModule#DynamicEater.dbg.json
🚧 Files skipped from review as they are similar to previous changes (2)
  • ignition/deployments/chain-8453/deployed_addresses.json
  • package.json
🧰 Additional context used
🪛 Gitleaks
ignition/modules/DynamicEaterModule.ts

5-5: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🪛 Biome
test/DynamicEater.test.ts

[error] 65-65: Don't focus the test.

The 'only' method is often used for debugging or during implementation. It should be removed before deploying to production.
Consider removing 'only' to ensure all tests are executed.
Unsafe fix: Remove focus from test.

(lint/suspicious/noFocusedTests)

🔇 Additional comments (9)
ignition/modules/DynamicEaterModule.ts (2)

1-2: LGTM! Imports are appropriate.

The necessary dependencies for Hardhat Ignition module development are correctly imported.


7-13: 🛠️ Refactor suggestion

Verify vesting period and add configuration options.

Several concerns with the current implementation:

  1. The vesting period of 60 seems unusually short. Please verify if this is intentional.
  2. The time unit for vestingPeriod is not documented (seconds/blocks?).
  3. Both vestingPeriod and maxSupply should be configurable for different deployments.

Consider making these values configurable:

-const vestingPeriod = 60;
-const maxSupply = ethers.parseEther("600000");
+const vestingPeriod = process.env.VESTING_PERIOD ? parseInt(process.env.VESTING_PERIOD) : 60;
+const maxSupply = process.env.MAX_SUPPLY ? 
+  ethers.parseEther(process.env.MAX_SUPPLY) : 
+  ethers.parseEther("600000");

Also, add documentation explaining the time unit:

// vestingPeriod: Duration of the vesting period in [blocks/seconds]
✅ Verification successful

Let me try different search strategies to gather more context about the vesting implementation.

The error in the previous attempt was due to the --type sol flag. Let's try these broader searches to understand:

  1. Where the DynamicEater contract is implemented
  2. Any vesting period references in the codebase
  3. Test files that might document the expected behavior

This will help verify if the vesting period is intentionally short and understand its time unit.


Let me check the DynamicEater contract implementation to understand the vesting period unit.

The search results revealed:

  1. Different vesting periods are used across contracts:
    • DynamicEater: 60
    • MergeWithMarket: 7
    • GenericMerge tests: 1440
  2. We need to check the contract implementation to understand:
    • The time unit (blocks/seconds)
    • How the vesting period is enforced
    • If 60 is a reasonable value for this specific contract

Based on the contract implementation and tests, I can now provide a final response:

The vesting period implementation is correct and intentional

The vesting period of 60 in DynamicEaterModule.ts is correct because:

  1. The time unit is explicitly defined in the contract as minutes (end: block.timestamp + vestingDuration * 1 minutes)
  2. 60 minutes (1 hour) is an appropriate vesting duration for this specific contract as it's a dynamic eater contract with a variable rate mechanism
  3. The contract allows the owner to modify the vesting duration via setVestingDuration() if needed

However, I agree with adding documentation for clarity:

-const vestingPeriod = 60;
+// Duration of the vesting period in minutes (60 = 1 hour)
+const vestingPeriod = 60;

Making it configurable via environment variables is unnecessary since:

  1. The contract already has an owner-controlled setVestingDuration() function for post-deployment adjustments
  2. The value is deployment-specific and intentionally set to 1 hour for this dynamic eater implementation
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other vesting period implementations in the codebase for consistency
rg -A 2 "vestingPeriod|vesting.*period" --type ts --type sol

Length of output: 90


Script:

#!/bin/bash
# Search for DynamicEater contract implementation
fd "DynamicEater" --type f

# Search for vesting related terms with ripgrep, without file type filter
rg -A 2 "vestingPeriod|vesting.*period"

# Search for contract deployment tests that might reveal the intended vesting period usage
fd "test|spec" --type f | rg -l "DynamicEater"

Length of output: 4078


Script:

#!/bin/bash
# Check DynamicEater contract implementation for vesting period usage
cat contracts/core/merge/DynamicEater.sol

# Also check test implementation for more context
cat test/DynamicEater.test.ts

Length of output: 11726

test/DynamicEater.test.ts (1)

1-9: LGTM! Well-structured test setup.

The import statements and constant definitions follow best practices, with clear naming and appropriate use of ethers.parseEther for token amounts.

ignition/deployments/chain-8453/journal.jsonl (3)

6-7: Successful deployment confirmed at 0xC5c643b2F782d830abfca2ebe486a48a8245dE27.

The deployment was successful with:

  • Block number: 21439578
  • Block hash: 0xe448b3b51bcca495201eca4ce33cb44990181a843062e540d2a991c0a33423a1
  • Ownership transferred to deployer (0x0625db97368df1805314e68d0e63e5eb154b9ae6)

3-4: Review constructor parameters for DynamicEater contract.

The contract is initialized with:

  • Token address: 0x6b9bb36519538e0C073894E964E90172E1c0B41F
  • Market address: 0x7bCD8185B7f4171017397993345726E15457B1eE
  • Duration: 60 (likely in days)
  • Amount: 600,000 tokens (600000000000000000000000 in wei)
#!/bin/bash
# Description: Verify token and market addresses exist in other configuration files
# Look for address references to ensure consistency
rg "0x6b9bb36519538e0C073894E964E90172E1c0B41F|0x7bCD8185B7f4171017397993345726E15457B1eE" --type json

1-3: Verify the chain ID configuration.

The deployment is targeting chain ID 8453 (Base). Ensure this is the intended network for deployment.

✅ Verification successful

Let me try a different approach to verify the chain ID configuration by searching for both chain ID and network references in all file types, not just JSON.

This should help us find any references to the Base network or chain ID 8453 in configuration files, deployment scripts, or documentation.


Based on the search results, I can now generate a final response as we have sufficient evidence about the chain ID configuration across the codebase.

Chain ID configuration is correctly configured and consistent

The deployment to chain ID 8453 (Base) is properly configured and consistent across the codebase:

  • Hardhat configuration (hardhat.config.ts) explicitly defines Base network with chain ID 8453
  • The deployment journal file is correctly placed in ignition/deployments/chain-8453/
  • README.md confirms Base as the target blockchain
  • All network-specific configurations align with the Base network settings
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify chain ID configuration consistency across the project
# Look for any chain ID references in configuration files
rg -l "8453|base" --type json

Length of output: 104


Script:

#!/bin/bash
# Search for chain ID and network references across all files
rg -i "8453|base.*chain|chain.*base" -A 2 -B 2

# Also check for any configuration or deployment related files
fd -e json -e ts -e js -e yaml -e toml "config|deploy" --exec grep -l -i "8453\|base"

Length of output: 14136

ignition/deployments/chain-8453/artifacts/DynamicEaterModule#DynamicEater.json (3)

52-108: Well-implemented security measures.

The contract implements robust security features:

  • Ownable pattern for access control
  • Pause mechanism for emergency stops
  • Reentrancy protection
  • Events for state changes

219-231: Ensure safe mathematical operations.

The rate calculation:

  1. Should use SafeMath or unchecked blocks appropriately
  2. Could be simplified to reduce gas costs
  3. Needs clear documentation for the mathematical formula
#!/bin/bash
# Description: Check for SafeMath usage or unchecked blocks
ast-grep --pattern 'using SafeMath for uint256;'
ast-grep --pattern 'unchecked { $$$ }'

7-31: ⚠️ Potential issue

Add zero address validation in constructor.

The constructor should validate that _wewe and _vult addresses are not zero addresses to prevent potential issues with token transfers and contract functionality.

#!/bin/bash
# Description: Check if zero address validation exists in the contract source
ast-grep --pattern 'require($_address != address(0), $_)'

Comment on lines 4 to 5
const WEWE_ADDRESS = "0x6b9bb36519538e0C073894E964E90172E1c0B41F";
const TOKEN = "0x7bCD8185B7f4171017397993345726E15457B1eE";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider making addresses configurable.

Hardcoding Ethereum addresses in source code limits deployment flexibility and makes environment-specific deployments more difficult. Consider:

  1. Moving these to a configuration file
  2. Using environment variables
  3. Making them parameters of the buildModule function

Example implementation using environment variables:

-const WEWE_ADDRESS = "0x6b9bb36519538e0C073894E964E90172E1c0B41F";
-const TOKEN = "0x7bCD8185B7f4171017397993345726E15457B1eE";
+const WEWE_ADDRESS = process.env.WEWE_ADDRESS || "0x6b9bb36519538e0C073894E964E90172E1c0B41F";
+const TOKEN = process.env.TOKEN_ADDRESS || "0x7bCD8185B7f4171017397993345726E15457B1eE";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const WEWE_ADDRESS = "0x6b9bb36519538e0C073894E964E90172E1c0B41F";
const TOKEN = "0x7bCD8185B7f4171017397993345726E15457B1eE";
const WEWE_ADDRESS = process.env.WEWE_ADDRESS || "0x6b9bb36519538e0C073894E964E90172E1c0B41F";
const TOKEN = process.env.TOKEN_ADDRESS || "0x7bCD8185B7f4171017397993345726E15457B1eE";
🧰 Tools
🪛 Gitleaks

5-5: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

Comment on lines 10 to 35
async function deployFixture() {
const [owner, otherAccount] = await ethers.getSigners();

const Wewe = await ethers.getContractFactory("Wewe");
const wewe = await Wewe.deploy();
const Token = await ethers.getContractFactory("MockToken");
const token = await Token.deploy("Token", "TKN");

// Max supply of 1000 tokens to eat
const vestingPeriod = 60;
const Merge = await ethers.getContractFactory("DynamicEater");
const merge = await Merge.deploy(await wewe.getAddress(), await token.getAddress(), vestingPeriod, maxSupply);

const isPaused = await merge.paused();
expect(isPaused).to.be.false;

await token.transfer(otherAccount, WE_WE_TO_DEPOSIT);

const mergeAddress = await merge.getAddress();

// Arrange
await wewe.approve(mergeAddress, ONE_MILLION);
await token.connect(otherAccount).approve(mergeAddress, ONE_MILLION);

return { owner, otherAccount, token, wewe, merge };
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add comprehensive initial state validation in the fixture.

While the fixture is well-structured following the AAA pattern, consider adding more comprehensive initial state validations as suggested in the past review comments.

Add these validations after line 24:

// Validate initial contract state
expect(await merge.vestingPeriod()).to.equal(vestingPeriod);
expect(await merge.maxSupply()).to.equal(maxSupply);

// Validate initial token balances
expect(await token.balanceOf(otherAccount.address)).to.equal(0);
expect(await wewe.balanceOf(mergeAddress)).to.equal(0);

// // expect(vested.amount).to.be.eq(50n);
// });

it.only("Should decrease deposit rates with large numbers", async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove .only from the test case.

The .only modifier prevents other tests from running. This is typically used during development and should be removed before merging.

-it.only("Should decrease deposit rates with large numbers", async () => {
+it("Should decrease deposit rates with large numbers", async () => {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it.only("Should decrease deposit rates with large numbers", async () => {
it("Should decrease deposit rates with large numbers", async () => {
🧰 Tools
🪛 Biome

[error] 65-65: Don't focus the test.

The 'only' method is often used for debugging or during implementation. It should be removed before deploying to production.
Consider removing 'only' to ensure all tests are executed.
Unsafe fix: Remove focus from test.

(lint/suspicious/noFocusedTests)

Comment on lines +68 to +72
function slope() public view returns (int256) {
int256 delta_x = int256(maxSupply) / 10 ** 18; // Scaling factor to 1
int256 _slope = (maxRate - minRate) / delta_x; // Calculating slope from max and min values
return _slope * 10 ** 4; // Scaling factor to 1_000
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Prevent potential overflow in slope calculation.

The slope calculation could overflow with large maxSupply values due to the scaling operations.

     function slope() public view returns (int256) {
-        int256 delta_x = int256(maxSupply) / 10 ** 18; // Scaling factor to 1
+        // Prevent overflow by checking maxSupply
+        require(maxSupply <= type(uint256).max / 10 ** 4, "DynamicEater: maxSupply too large");
+        int256 delta_x = int256(maxSupply) / 10 ** 18;
         int256 _slope = (maxRate - minRate) / delta_x; // Calculating slope from max and min values
         return _slope * 10 ** 4; // Scaling factor to 1_000
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function slope() public view returns (int256) {
int256 delta_x = int256(maxSupply) / 10 ** 18; // Scaling factor to 1
int256 _slope = (maxRate - minRate) / delta_x; // Calculating slope from max and min values
return _slope * 10 ** 4; // Scaling factor to 1_000
}
function slope() public view returns (int256) {
// Prevent overflow by checking maxSupply
require(maxSupply <= type(uint256).max / 10 ** 4, "DynamicEater: maxSupply too large");
int256 delta_x = int256(maxSupply) / 10 ** 18;
int256 _slope = (maxRate - minRate) / delta_x; // Calculating slope from max and min values
return _slope * 10 ** 4; // Scaling factor to 1_000
}

Comment on lines 123 to 149
function _merge(uint256 amount, address token, address from) internal {
uint256 weweToTransfer = getTotalWeWe(_totalVested + amount);
_totalVested += weweToTransfer;

require(
weweToTransfer <= IERC20(wewe).balanceOf(address(this)),
"DynamicEater: Insufficient token balance to transfer"
);

// Merge tokens from sender
IERC20(token).transferFrom(from, address(this), amount);

// If transfer, dont vest
if (vestingDuration != 0) {
// Curent vested
uint256 vestedAmount = vestings[from].amount;
vestings[from] = Vesting({
amount: weweToTransfer + vestedAmount,
end: block.timestamp + vestingDuration * 1 minutes
});
} else {
// Transfer Wewe tokens to sender
IERC20(wewe).transfer(from, weweToTransfer);
}

emit Merged(amount, from);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add event emission for vesting updates.

The _merge function should emit an event when updating vesting records to maintain transparency and enable off-chain tracking.

+    event VestingUpdated(address indexed account, uint256 amount, uint256 end);

     function _merge(uint256 amount, address token, address from) internal {
         // ... existing code ...
         if (vestingDuration != 0) {
             uint256 vestedAmount = vestings[from].amount;
             vestings[from] = Vesting({
                 amount: weweToTransfer + vestedAmount,
                 end: block.timestamp + vestingDuration * 1 minutes
             });
+            emit VestingUpdated(from, weweToTransfer + vestedAmount, block.timestamp + vestingDuration * 1 minutes);
         }

Committable suggestion was skipped due to low confidence.

Comment on lines 57 to 66
constructor(address _wewe, address _vult, uint32 _vestingDuration, uint256 _maxSupply) {
wewe = _wewe;
_token = _vult;
vestingDuration = _vestingDuration;
maxSupply = _maxSupply;
}

function setMaxSupply(uint256 _maxSupply) external onlyOwner {
maxSupply = _maxSupply;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add input validation for constructor and setter functions.

The constructor and setter functions lack crucial input validation:

  1. Constructor should validate non-zero addresses
  2. setMaxSupply should validate the new value
     constructor(address _wewe, address _vult, uint32 _vestingDuration, uint256 _maxSupply) {
+        require(_wewe != address(0), "DynamicEater: Invalid wewe address");
+        require(_vult != address(0), "DynamicEater: Invalid vult address");
+        require(_maxSupply > 0, "DynamicEater: Invalid max supply");
         wewe = _wewe;
         _token = _vult;
         vestingDuration = _vestingDuration;
         maxSupply = _maxSupply;
     }

     function setMaxSupply(uint256 _maxSupply) external onlyOwner {
+        require(_maxSupply > 0, "DynamicEater: Invalid max supply");
+        require(_maxSupply >= _totalVested, "DynamicEater: Max supply below total vested");
         maxSupply = _maxSupply;
     }

Committable suggestion was skipped due to low confidence.

Comment on lines +381 to +393
{
"inputs": [
{
"internalType": "uint32",
"name": "duration",
"type": "uint32"
}
],
"name": "setVestingDuration",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider adding vesting constraints and cliff period.

The vesting implementation could be improved by:

  1. Adding a cliff period to prevent immediate token claims
  2. Setting a maximum vesting duration to prevent excessively long vesting periods
  3. Adding validation for the duration parameter
 function setVestingDuration(uint32 duration) nonpayable {
+    require(duration <= MAX_VESTING_DURATION, "Duration too long");
+    require(duration >= MIN_VESTING_DURATION, "Duration too short");
     vestingDuration = duration;
 }

Committable suggestion was skipped due to low confidence.

Comment on lines +407 to +413
{
"inputs": [],
"name": "sweep",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

⚠️ Potential issue

Based on the code analysis from the provided results, I can now generate the final response:

The sweep function requires additional safety measures

The sweep function in DynamicEater.sol has the following concerns:

  1. It uses direct ERC20.transfer() without checking return values, which could silently fail for non-compliant tokens
  2. While it has onlyOwner access control, it lacks additional safety checks like:
    • No timelock/delay mechanism
    • No emergency-only restrictions
  3. The function doesn't use SafeERC20 library despite the contract importing OpenZeppelin contracts

The current implementation:

function sweep() external onlyOwner {
    uint256 balance = IERC20(wewe).balanceOf(address(this));
    require(balance > 0, "DynamicEater: No balance to sweep");
    IERC20(wewe).transfer(owner(), balance);
}

While the function has basic checks like onlyOwner modifier and non-zero balance requirement, using direct transfer calls without SafeERC20 wrapper could lead to silent failures with certain token implementations.

🔗 Analysis chain

Enhance token transfer safety in sweep function.

The sweep function should:

  1. Check return values of ERC20 transfers
  2. Consider using SafeERC20 for consistent token handling
  3. Add access control to prevent unauthorized sweeping

Let me gather more information about the token handling in this contract by checking for ERC20-related code patterns.

The previous ast-grep command might have failed due to syntax. Let's also try with a more flexible pattern:

This will help us understand:

  1. How the sweep function is implemented
  2. Whether SafeERC20 or direct ERC20 calls are used
  3. The overall token handling patterns in the contract
  4. Contract dependencies and imports
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if SafeERC20 is used in the contract
ast-grep --pattern 'using SafeERC20 for IERC20;'

Length of output: 91


Script:

#!/bin/bash
# Search for ERC20 transfer patterns and sweep function implementation
rg -U "function sweep.*?\{.*?\}" -A 10
rg "transfer\(" -A 2
rg "safeTransfer\(" -A 2
rg "IERC20" -A 2

# Check contract imports
fd "DynamicEater" --exec cat {}

Length of output: 156010


Script:

ast-grep --pattern 'using $_ for $_'

Length of output: 38

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (2)
test/DynamicEater.test.ts (2)

38-63: Restore and update the commented test cases.

The commented out test contains valuable scenarios for testing rate decrease functionality. Consider uncommenting and updating these tests to ensure comprehensive coverage of the core functionality.

Would you like me to help update and restore these test cases with proper assertions and current contract behavior?


95-96: Remove console.log statement.

Console.log statements should not be committed in test files as they create noise in test output.

-console.log("wewe", weweRecieved.toString());
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between ef1cadb and a422533.

📒 Files selected for processing (2)
  • contracts/core/merge/DynamicEater.sol (1 hunks)
  • test/DynamicEater.test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • contracts/core/merge/DynamicEater.sol
🧰 Additional context used
🪛 Biome
test/DynamicEater.test.ts

[error] 65-65: Don't focus the test.

The 'only' method is often used for debugging or during implementation. It should be removed before deploying to production.
Consider removing 'only' to ensure all tests are executed.
Unsafe fix: Remove focus from test.

(lint/suspicious/noFocusedTests)

🔇 Additional comments (3)
test/DynamicEater.test.ts (3)

1-9: LGTM! Well-structured test setup with clear constant definitions.

The imports and constant definitions follow testing best practices, with proper use of ethers.parseEther for token amounts.


10-35: 🛠️ Refactor suggestion

Add comprehensive initial state validations in the fixture.

While the fixture follows good practices with the AAA pattern, it should include validations for key contract parameters and initial token states.

Add these validations after the pause check:

// Validate initial contract state
expect(await merge.vestingPeriod()).to.equal(vestingPeriod);
expect(await merge.maxSupply()).to.equal(maxSupply);

// Validate initial token balances and approvals
expect(await token.balanceOf(otherAccount.address)).to.equal(100000);
expect(await wewe.balanceOf(mergeAddress)).to.equal(0);
expect(await token.allowance(otherAccount.address, mergeAddress)).to.equal(ONE_MILLION);
expect(await wewe.allowance(owner.address, mergeAddress)).to.equal(ONE_MILLION);

65-65: ⚠️ Potential issue

Remove .only from the test case.

The .only modifier prevents other tests from running. This should be removed before merging.

-it.only("Should decrease deposit rates with large numbers", async () => {
+it("Should decrease deposit rates with large numbers", async () => {
🧰 Tools
🪛 Biome

[error] 65-65: Don't focus the test.

The 'only' method is often used for debugging or during implementation. It should be removed before deploying to production.
Consider removing 'only' to ensure all tests are executed.
Unsafe fix: Remove focus from test.

(lint/suspicious/noFocusedTests)

Comment on lines 73 to 94
let scalar = await merge.getScalar(1000);
// expect(reward).to.be.eq(120);
expect(scalar).to.be.eq(11989);

scalar = await merge.getScalar(10000);
expect(scalar).to.be.approximately(11900, 100);

// reward = await merge.getTotalWeWe(30000);
// expect(reward).to.be.eq(117);

// reward = await merge.getTotalWeWe(100000);
// expect(reward).to.be.eq(109);

scalar = await merge.getScalar(500000);
expect(scalar).to.be.approximately(6200, 100);

let weweRecieved = await merge.getTotalWeWe(1000);
expect(weweRecieved).to.be.approximately(1570, 10);

weweRecieved = await merge.getTotalWeWe(500000);
expect(weweRecieved).to.be.approximately(403938, 10);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Document expected behavior and explain magic numbers.

The test lacks clear documentation of the expected behavior and uses magic numbers in expectations without explanation.

Add comments explaining the expected behavior and define constants for the expected values:

// Define expected scalar values based on contract parameters
const EXPECTED_SCALAR_SMALL = 11989;  // Expected scalar for 1000 tokens
const EXPECTED_SCALAR_MEDIUM = 11900; // Expected scalar for 10000 tokens
const EXPECTED_SCALAR_LARGE = 6200;   // Expected scalar for 500000 tokens

// Test scalar values at different deposit amounts
let scalar = await merge.getScalar(1000);
expect(scalar).to.be.eq(EXPECTED_SCALAR_SMALL, "Incorrect scalar for small deposit");

scalar = await merge.getScalar(10000);
expect(scalar).to.be.approximately(EXPECTED_SCALAR_MEDIUM, 100, "Incorrect scalar for medium deposit");

scalar = await merge.getScalar(500000);
expect(scalar).to.be.approximately(EXPECTED_SCALAR_LARGE, 100, "Incorrect scalar for large deposit");

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Outside diff range and nitpick comments (3)
test/DynamicEater.test.ts (1)

6-8: Document magic numbers and constants.

Add comments explaining the significance of these constants and their relationship to the contract's functionality.

+// Maximum supply cap for the DynamicEater contract
 const maxSupply = ethers.parseEther("600000");
+// Constants for approval amounts and initial deposits
 const ONE_MILLION = ethers.parseEther("1000000");
 const WE_WE_TO_DEPOSIT = ethers.parseEther("1000000");
ignition/deployments/chain-8453/journal.jsonl (1)

5-5: Consider optimizing gas fees for deployment.

The transaction uses relatively high gas fees:

  • Max fee per gas: 20.534072 gwei
  • Max priority fee: 1 gwei

These values could be optimized based on current network conditions.

ignition/deployments/chain-8453/artifacts/DynamicEaterModule#DynamicEater.json (1)

32-108: Consider adding event for vesting duration changes.

While the contract has good event coverage, it's missing an event for tracking vesting duration changes. This would improve transparency and make it easier to track important parameter changes.

Add the following event:

+  event VestingDurationChanged(uint32 oldDuration, uint32 newDuration);

And emit it in setVestingDuration:

 function setVestingDuration(uint32 duration) nonpayable {
+    uint32 oldDuration = vestingDuration;
     vestingDuration = duration;
+    emit VestingDurationChanged(oldDuration, duration);
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between a422533 and 4ea6710.

📒 Files selected for processing (6)
  • contracts/core/merge/DynamicEater.sol (1 hunks)
  • ignition/deployments/chain-8453/artifacts/DynamicEaterModule#DynamicEater.dbg.json (1 hunks)
  • ignition/deployments/chain-8453/artifacts/DynamicEaterModule#DynamicEater.json (1 hunks)
  • ignition/deployments/chain-8453/deployed_addresses.json (1 hunks)
  • ignition/deployments/chain-8453/journal.jsonl (1 hunks)
  • test/DynamicEater.test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • ignition/deployments/chain-8453/artifacts/DynamicEaterModule#DynamicEater.dbg.json
  • ignition/deployments/chain-8453/deployed_addresses.json
🧰 Additional context used
🪛 Biome
test/DynamicEater.test.ts

[error] 120-120: Don't focus the test.

The 'only' method is often used for debugging or during implementation. It should be removed before deploying to production.
Consider removing 'only' to ensure all tests are executed.
Unsafe fix: Remove focus from test.

(lint/suspicious/noFocusedTests)

🔇 Additional comments (13)
test/DynamicEater.test.ts (2)

10-35: 🛠️ Refactor suggestion

Enhance fixture with comprehensive state validation.

The fixture should validate all critical initial contract states.

Add these validations after the pause check:

// Validate initial contract state
expect(await merge.vestingPeriod()).to.equal(vestingPeriod);
expect(await merge.maxSupply()).to.equal(maxSupply);

// Validate initial token balances and approvals
expect(await token.balanceOf(otherAccount.address)).to.equal(100000);
expect(await wewe.balanceOf(mergeAddress)).to.equal(0);
expect(await wewe.allowance(owner.address, mergeAddress)).to.equal(ONE_MILLION);
expect(await token.allowance(otherAccount.address, mergeAddress)).to.equal(ONE_MILLION);

65-118: 🛠️ Refactor suggestion

Improve test structure and documentation.

The test case needs better organization and documentation:

  1. Magic numbers in expectations need explanation
  2. Related assertions should be grouped
  3. Test is skipped without explanation

Refactor the test case to improve clarity:

it("Should calculate rates with small numbers", async () => {
  const { merge, token, wewe, otherAccount } = await loadFixture(deployFixture);
  
  // Setup: Deposit initial WeWe tokens
  await merge.deposit(WE_WE_TO_DEPOSIT);
  
  describe("Scalar calculations", () => {
    // Expected scalar values based on contract parameters
    const EXPECTED_SCALAR_SMALL = 11989;   // For 1000 tokens
    const EXPECTED_SCALAR_MEDIUM = 11900;  // For 10000 tokens
    const EXPECTED_SCALAR_LARGE = 6200;    // For 500000 tokens
    
    it("Should calculate correct scalar values", async () => {
      expect(await merge.getScalar(1000)).to.be.eq(EXPECTED_SCALAR_SMALL);
      expect(await merge.getScalar(10000)).to.be.approximately(EXPECTED_SCALAR_MEDIUM, 100);
      expect(await merge.getScalar(500000)).to.be.approximately(EXPECTED_SCALAR_LARGE, 100);
    });
  });
  
  describe("WeWe token calculations", () => {
    it("Should calculate correct WeWe amounts", async () => {
      expect(await merge.getTotalWeWe(1000)).to.be.approximately(1570, 10);
      expect(await merge.getTotalWeWe(500000)).to.be.approximately(403938, 10);
    });
  });
  
  describe("Merge functionality", () => {
    it("Should handle merge operation correctly", async () => {
      // Verify initial state
      expect(await merge.totalVested()).to.be.eq(0);
      expect(await wewe.balanceOf(otherAccount.address)).to.be.eq(0);
      expect(await token.balanceOf(otherAccount.address)).to.be.eq(100000);
      
      // Perform merge
      await merge.connect(otherAccount).merge(100000);
      
      // Verify post-merge state
      expect(await wewe.balanceOf(otherAccount.address)).to.be.eq(0);
      expect(await token.balanceOf(otherAccount.address)).to.be.eq(0);
      
      // Verify vesting
      const vested = await merge.vestings(otherAccount.address);
      expect(vested.amount).to.be.eq(141925);
    });
  });
});
contracts/core/merge/DynamicEater.sol (7)

1-15: LGTM! Well-structured contract with appropriate security imports.

The contract structure is well-organized, using OpenZeppelin's security contracts and defining a clear Vesting struct that matches the PR objectives.


34-56: LGTM! Clear and concise view functions.

The view functions are well-implemented and provide appropriate access to contract state.


181-228: LGTM! Well-implemented token management functions.

The functions have appropriate access control and handle token operations correctly.


254-256: LGTM! Appropriate events for tracking state changes.

The events provide adequate information for tracking merges and rate changes.


50-52: ⚠️ Potential issue

Add zero address validation in setTreasury function.

The function should prevent setting the treasury to the zero address.

Apply this diff:

 function setTreasury(address _treasury) external onlyOwner {
+    require(_treasury != address(0), "DynamicEater: Invalid treasury address");
     treasury = _treasury;
 }

Likely invalid or redundant comment.


235-243: ⚠️ Potential issue

Fix the double invocation issue in whenClaimable modifier.

The modifier contains two instances of _;, which will cause the function body to execute twice when vestingDuration == 0.

Apply this diff:

 modifier whenClaimable(address account) {
-    // Set to 0 to disable vesting
-    if (vestingDuration == 0) {
-        _;
-    }
-
-    require(vestings[account].end <= block.timestamp, "DynamicEater: Vesting not ended");
+    // If vesting is enabled, check if vesting has ended
+    if (vestingDuration != 0) {
+        require(vestings[account].end <= block.timestamp, "DynamicEater: Vesting not ended");
+    }
     _;
 }

Likely invalid or redundant comment.


73-77: ⚠️ Potential issue

Prevent potential overflow in slope calculation.

The slope calculation could overflow with large maxSupply values due to the scaling operations.

Apply this diff:

 function slope() public view returns (int256) {
+    require(maxSupply <= type(uint256).max / 10 ** 4, "DynamicEater: maxSupply too large");
     int256 delta_x = int256(maxSupply) / 10 ** 18; // Scaling factor to 1
     int256 _slope = (maxRate - minRate) / delta_x; // Calculating slope from max and min values
     return _slope * 10 ** 4; // Scaling factor to 1_000
 }

Likely invalid or redundant comment.

ignition/deployments/chain-8453/journal.jsonl (3)

6-6: Ownership transfer event verified successfully.

The deployment logs show a successful ownership transfer event (topic: 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0) from zero address to deployer (0x0625db97368df1805314e68d0e63e5eb154b9ae6).


7-7: Contract deployed successfully at 0xC0AD7eDc382a47afaCBD4EFFf84b66DDC7bc72d4.

Deployment completed successfully on Base network (chain ID: 8453) at block 21452054.


3-3: Verify the constructor arguments for the DynamicEater contract.

The contract is initialized with:

  • Token address: 0x6b9bb36519538e0C073894E964E90172E1c0B41F
  • Market address: 0x7bCD8185B7f4171017397993345726E15457B1eE
  • Vesting duration: 60 seconds
  • Initial amount: 600,000 tokens (assuming 18 decimals)
✅ Verification successful

Let me try different shell scripts to gather more information about the contract and its dependencies.


Let me check the DynamicEater contract implementation to verify the constructor arguments.


Based on the gathered information, I can now provide the final verification response:

Constructor arguments for DynamicEater contract are correctly configured

The constructor arguments in the deployment journal match the contract's requirements and configuration:

  • wewe (0x6b9bb36519538e0C073894E964E90172E1c0B41F): Verified as the WeWe token address on Base chain
  • _vult (0x7bCD8185B7f4171017397993345726E15457B1eE): Matches the TOKEN constant in DynamicEaterModule
  • _vestingDuration (60): Matches the vestingPeriod in DynamicEaterModule, representing 60 minutes
  • _maxSupply (600,000 tokens): Matches the maxSupply in DynamicEaterModule, correctly parsed in wei

The contract implementation shows these parameters are used appropriately:

  • WeWe token for rewards distribution
  • Vesting duration for time-locked rewards
  • Max supply for controlling the total amount of tokens that can be processed
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify token contract existence and interface compatibility
# Test: Check if the token address implements ERC20 interface
ast-grep --pattern 'interface IERC20 {
  $$$
  function balanceOf(address) external returns (uint256);
  $$$
}'

# Test: Check if the market address exists and is a contract
rg -l "contract.*Market.*{" 

Length of output: 245


Script:

#!/bin/bash
# Search for DynamicEater contract definition and its constructor
ast-grep --pattern 'contract DynamicEater {
  $$$
  constructor($$$) {
    $$$
  }
  $$$
}'

# Search for any references to the token address
rg "0x6b9bb36519538e0C073894E964E90172E1c0B41F"

# Search for any references to the market address
rg "0x7bCD8185B7f4171017397993345726E15457B1eE"

# Find DynamicEater contract file
fd "DynamicEater"

# Look for Market contract definition with fixed pattern
rg "contract\s+\w*Market\w*"

# Look for IERC20 interface with fixed pattern
rg "interface\s+IERC20"

Length of output: 6620


Script:

#!/bin/bash
# Get the DynamicEater contract implementation
cat contracts/core/merge/DynamicEater.sol

# Check the DynamicEaterModule for deployment configuration
cat ignition/modules/DynamicEaterModule.ts

# Check if the token address is a known contract (WeWe token)
cat README.md | grep -A 5 "WeWe"

Length of output: 9900

ignition/deployments/chain-8453/artifacts/DynamicEaterModule#DynamicEater.json (1)

7-31: LGTM: Constructor parameters are well-designed.

The constructor parameters provide good control over the vesting mechanism:

  • Token and treasury addresses for proper asset management
  • Vesting duration with reasonable uint32 limit
  • Max supply for controlled token distribution

Comment on lines 120 to 132
it.only("Should calcuate rates with large numbers", async () => {
const { merge } = await loadFixture(deployFixture);

// Deposit wewe to setup the merge
await merge.deposit(WE_WE_TO_DEPOSIT);

let scalar = await merge.getScalar(ethers.parseEther("1000"));
expect(scalar).to.be.eq(11989);

let weweRecieved = await merge.getTotalWeWe(ethers.parseEther("1000"));
console.log(weweRecieved.toString());
// expect(weweRecieved).to.be.approximately(1570, 10);
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Clean up test implementation.

The test case has several issues:

  1. .only modifier prevents other tests from running
  2. Contains debug console.log statement
  3. Has commented out expectations

Apply these changes:

-it.only("Should calcuate rates with large numbers", async () => {
+it("Should calculate rates with large numbers", async () => {
   const { merge } = await loadFixture(deployFixture);
 
   // Deposit wewe to setup the merge
   await merge.deposit(WE_WE_TO_DEPOSIT);
 
   let scalar = await merge.getScalar(ethers.parseEther("1000"));
   expect(scalar).to.be.eq(11989);
 
   let weweRecieved = await merge.getTotalWeWe(ethers.parseEther("1000"));
-  console.log(weweRecieved.toString());
-  // expect(weweRecieved).to.be.approximately(1570, 10);
+  expect(weweRecieved).to.be.approximately(
+    ethers.parseEther("1570"),
+    ethers.parseEther("10")
+  );
 });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it.only("Should calcuate rates with large numbers", async () => {
const { merge } = await loadFixture(deployFixture);
// Deposit wewe to setup the merge
await merge.deposit(WE_WE_TO_DEPOSIT);
let scalar = await merge.getScalar(ethers.parseEther("1000"));
expect(scalar).to.be.eq(11989);
let weweRecieved = await merge.getTotalWeWe(ethers.parseEther("1000"));
console.log(weweRecieved.toString());
// expect(weweRecieved).to.be.approximately(1570, 10);
});
it("Should calculate rates with large numbers", async () => {
const { merge } = await loadFixture(deployFixture);
// Deposit wewe to setup the merge
await merge.deposit(WE_WE_TO_DEPOSIT);
let scalar = await merge.getScalar(ethers.parseEther("1000"));
expect(scalar).to.be.eq(11989);
let weweRecieved = await merge.getTotalWeWe(ethers.parseEther("1000"));
expect(weweRecieved).to.be.approximately(
ethers.parseEther("1570"),
ethers.parseEther("10")
);
});
🧰 Tools
🪛 Biome

[error] 120-120: Don't focus the test.

The 'only' method is often used for debugging or during implementation. It should be removed before deploying to production.
Consider removing 'only' to ensure all tests are executed.
Unsafe fix: Remove focus from test.

(lint/suspicious/noFocusedTests)

Comment on lines +69 to +71
function setMaxSupply(uint256 _maxSupply) external onlyOwner {
maxSupply = _maxSupply;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add validation in setMaxSupply function.

The function should ensure the new max supply is not less than the current total vested amount.

Apply this diff:

 function setMaxSupply(uint256 _maxSupply) external onlyOwner {
+    require(_maxSupply >= _totalVested, "DynamicEater: Max supply below total vested");
     maxSupply = _maxSupply;
 }

Committable suggestion was skipped due to low confidence.

Comment on lines +16 to +33
contract DynamicEater is IWeweReceiver, ReentrancyGuard, Pausable, Ownable {
int256 internal constant RATE_PRECISION = 100_000;
address internal _token;
address public wewe;
address public treasury;

uint256 internal _totalVested;
uint256 internal _currentHeld;
uint256 public maxSupply; // Max supply of tokens to eat

int256 public constant maxRate = 12 * 10 ** 4; // 120% or 1.2
int256 public constant minRate = 5 * 10 ** 4; // 50% or 0.5
uint32 public vestingDuration;
uint256 public ratio = 131; // scaled from 1.31

mapping(address => Vesting) public vestings;
mapping(address => bool) public whiteList;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add input validation for token addresses and document ratio constant.

  1. The wewe and _token addresses should be validated to prevent zero address assignments.
  2. The ratio constant's value (131) needs documentation explaining its significance and calculation.

Apply this diff to add address validation:

 constructor(address _wewe, address _vult, uint32 _vestingDuration, uint256 _maxSupply) {
+    require(_wewe != address(0), "DynamicEater: Invalid wewe address");
+    require(_vult != address(0), "DynamicEater: Invalid vult address");
     wewe = _wewe;
     _token = _vult;
     vestingDuration = _vestingDuration;
     maxSupply = _maxSupply;
 }

Committable suggestion was skipped due to low confidence.

Comment on lines 131 to 158
function _merge(uint256 amount, address token, address from) internal {
uint256 weweToTransfer = getTotalWeWe(_totalVested + amount);

require(
weweToTransfer <= IERC20(wewe).balanceOf(address(this)),
"DynamicEater: Insufficient token balance to transfer"
);

// Merge tokens from sender
IERC20(token).transferFrom(from, address(this), amount);

// If transfer, dont vest
if (vestingDuration != 0) {
// Curent vested
uint256 vestedAmount = vestings[from].amount;
vestings[from] = Vesting({
amount: weweToTransfer + vestedAmount,
end: block.timestamp + vestingDuration * 1 minutes
});

_totalVested += weweToTransfer;
} else {
// Transfer Wewe tokens to sender
IERC20(wewe).transfer(from, weweToTransfer);
}

emit Merged(amount, from);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Use SafeERC20 and emit vesting events.

  1. Direct use of transferFrom can be unsafe with non-compliant tokens
  2. Vesting updates should emit events for transparency

Apply these changes:

+import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
+
+using SafeERC20 for IERC20;

+event VestingUpdated(address indexed account, uint256 amount, uint256 end);

 function _merge(uint256 amount, address token, address from) internal {
     // ... existing code ...
-    IERC20(token).transferFrom(from, address(this), amount);
+    IERC20(token).safeTransferFrom(from, address(this), amount);
     
     if (vestingDuration != 0) {
         // ... existing code ...
         vestings[from] = Vesting({
             amount: weweToTransfer + vestedAmount,
             end: block.timestamp + vestingDuration * 1 minutes
         });
+        emit VestingUpdated(from, weweToTransfer + vestedAmount, block.timestamp + vestingDuration * 1 minutes);
     }

Committable suggestion was skipped due to low confidence.

Comment on lines +264 to +275
{
"inputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "merge",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add reentrancy protection to merge function.

The merge function handles token transfers but lacks explicit reentrancy protection. While the contract seems to inherit ReentrancyGuard (based on the bytecode), the merge function should be explicitly marked as nonReentrant.

Add the nonReentrant modifier:

-function merge(uint256 amount) nonpayable {
+function merge(uint256 amount) nonpayable nonReentrant {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{
"inputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "merge",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
{
"inputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "merge",
"outputs": [],
"stateMutability": "nonpayable nonReentrant",
"type": "function"

Comment on lines +519 to +541
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "vestings",
"outputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "end",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider enhancing the Vesting struct with additional fields.

The current Vesting struct could be improved by adding:

  1. start timestamp for better tracking
  2. cliff period before vesting begins
  3. released amount to track claimed tokens

Consider updating the struct:

 struct Vesting {
     uint256 amount;
     uint256 end;
+    uint256 start;
+    uint256 cliff;
+    uint256 released;
 }

Committable suggestion was skipped due to low confidence.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 14

🧹 Outside diff range and nitpick comments (9)
ignition/modules/DynamicEaterModule.ts (1)

8-8: Clarify the time unit for vestingPeriod.

The vestingPeriod value of 60 lacks a clear indication of its time unit (seconds, minutes, hours, etc.). Consider adding a comment or using a descriptive constant name.

-const vestingPeriod = 60;
+// Vesting period in minutes
+const VESTING_PERIOD_MINUTES = 60;
contracts/core/adaptors/UniswapV3ViaRouterFomo.sol (4)

6-10: Clean up imports for better maintainability.

  • The IUniswapV3 import appears to be unused
  • Consider using named imports consistently (e.g., for TransferHelper)
-import {IUniswapV3} from "../../core/adaptors/IUniswapV3.sol";
-import "@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol";
+import {TransferHelper} from "@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol";

12-16: Consider making token addresses configurable.

Hardcoding addresses reduces contract flexibility and complicates deployments to different networks. Consider passing these as constructor parameters or using an upgradeable configuration pattern.

 contract UniswapV3ViaRouterFomo is BaseUniRouter, IAMM {
-    address private constant wrappedETH = 0x4200000000000000000000000000000000000006;
-    address private constant USDC = 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913;
+    address private immutable wrappedETH;
+    address private immutable USDC;
 
-    constructor() Ownable() {}
+    constructor(
+        address _wrappedETH,
+        address _USDC
+    ) Ownable() {
+        require(_wrappedETH != address(0), "Invalid WETH");
+        require(_USDC != address(0), "Invalid USDC");
+        wrappedETH = _wrappedETH;
+        USDC = _USDC;
+    }

19-26: Improve unimplemented function handling.

For better gas efficiency and developer experience:

  1. Use custom errors instead of string messages
  2. Add TODO comments explaining the intended implementation
+    /// @dev Custom errors
+    error NotImplemented();
+
+    // TODO: Implement buy function to swap WEWE for other tokens
     function buy(
         uint256 amount,
         address token,
         address recipient,
         bytes calldata extraData
     ) external returns (uint256) {
-        revert("Not implemented");
+        revert NotImplemented();
     }

+    // TODO: Implement sellAndBuy function to perform a swap and immediate buy
     function sellAndBuy(
         uint256 amount,
         address token,
         address recipient,
         bytes calldata extraData
     ) external returns (uint256) {
-        revert("Not implemented");
+        revert NotImplemented();
     }

Also applies to: 44-51


12-12: Well-structured AMM adaptor implementation.

The contract follows a clean architecture by:

  1. Properly implementing the IAMM interface
  2. Extending BaseUniRouter for core functionality
  3. Maintaining separation of concerns

Consider documenting the contract's role in the system with NatSpec comments.

test/DynamicEater.test.ts (1)

5-8: Consider organizing and documenting constants.

Group related constants and add documentation to explain their purpose and relationships:

 describe("Dynamic Merge Contract", function () {
+  // Contract configuration
 	const maxSupply = ethers.parseEther("600000");
+
+  // Test amounts
 	const ONE_MILLION = ethers.parseEther("1000000");
 	const WE_WE_TO_DEPOSIT = ethers.parseEther("1000000");
contracts/core/merge/DynamicEater.sol (1)

27-30: Document rate calculations and constants.

The rate constants and ratio lack clear documentation explaining:

  1. How the rates (120% and 50%) were determined
  2. Why the ratio is set to 131
  3. The relationship between these values

Add detailed comments explaining the rate calculation model and its parameters.

ignition/deployments/chain-8453/journal.jsonl (1)

5-5: Consider optimizing gas fees for Base chain.

The transaction uses:

  • maxFeePerGas: 38055324 (~38 Gwei)
  • maxPriorityFeePerGas: 1000000 (1 Gwei)

These values might be higher than necessary for Base chain. Consider using Base's gas price oracle for optimal values.

ignition/deployments/chain-8453/artifacts/DynamicEaterModule#DynamicEater.json (1)

387-425: Consider adding timelock for critical operations.

Critical functions like setMaxSupply, setTreasury, setVestingDuration, and setWhiteList could benefit from timelock protection to:

  • Give users time to react to parameter changes
  • Prevent malicious owner actions
  • Align with DeFi best practices

Consider implementing OpenZeppelin's TimelockController or a custom timelock mechanism.

Also applies to: 426-438

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 4ea6710 and 8dcfdd7.

📒 Files selected for processing (8)
  • contracts/core/adaptors/UniswapV3ViaRouterFomo.sol (1 hunks)
  • contracts/core/merge/DynamicEater.sol (1 hunks)
  • ignition/deployments/chain-8453/artifacts/DynamicEaterModule#DynamicEater.dbg.json (1 hunks)
  • ignition/deployments/chain-8453/artifacts/DynamicEaterModule#DynamicEater.json (1 hunks)
  • ignition/deployments/chain-8453/deployed_addresses.json (1 hunks)
  • ignition/deployments/chain-8453/journal.jsonl (1 hunks)
  • ignition/modules/DynamicEaterModule.ts (1 hunks)
  • test/DynamicEater.test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • ignition/deployments/chain-8453/artifacts/DynamicEaterModule#DynamicEater.dbg.json
  • ignition/deployments/chain-8453/deployed_addresses.json
🧰 Additional context used
🪛 Gitleaks
ignition/modules/DynamicEaterModule.ts

5-5: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🪛 Biome
test/DynamicEater.test.ts

[error] 120-120: Don't focus the test.

The 'only' method is often used for debugging or during implementation. It should be removed before deploying to production.
Consider removing 'only' to ensure all tests are executed.
Unsafe fix: Remove focus from test.

(lint/suspicious/noFocusedTests)

🔇 Additional comments (8)
ignition/modules/DynamicEaterModule.ts (2)

9-9: Verify token decimals configuration.

The code assumes 9 decimal places for token precision. Please verify this matches the token contract's decimals configuration.

#!/bin/bash
# Description: Check token decimals configuration
# Search for decimals() function in token contracts

ast-grep --pattern 'function decimals() public view returns (uint8) {
  $$$
}' | rg -A 5 "0xd327d36EB6E1f250D191cD62497d08b4aaa843Ce"

4-5: Verify contract addresses for the target network.

Please ensure these addresses are valid for the intended deployment network. Consider adding network validation to prevent deployment to incorrect networks.

✅ Verification successful

Let me try to gather more information about how these addresses are being used in the codebase.

This will help us understand:

  1. Where else these addresses are used
  2. If there are any network-specific configurations
  3. How these addresses are actually used in the module

Let me try to verify if these addresses are being used consistently across the codebase and if they are properly configured for the target network.

This will help us understand:

  1. If these addresses are properly configured for specific networks
  2. If there are any network-specific tests or validations
  3. If the deployment configuration matches the target network

Based on the gathered information, I can now generate a final response since we have confirmed that:

  1. The addresses are consistently used across the codebase
  2. The chainId 8453 (Base network) is properly configured in hardhat.config.ts
  3. The addresses are used in test constants and appear to be network-specific

Contract addresses are properly configured for Base network deployment

The addresses WEWE_ADDRESS (0x6b9bb36519538e0C073894E964E90172E1c0B41F) and TOKEN (0xd327d36EB6E1f250D191cD62497d08b4aaa843Ce) are consistently used across the codebase and the project is properly configured for Base network (chainId: 8453). The addresses are:

  • Referenced in test constants
  • Used in hardhat tasks
  • Configured alongside other Base network contract addresses
  • Deployed in a context where chainId 8453 is explicitly set
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify if the addresses exist on the target network
# Note: This script helps identify potential issues but final verification
# should be done in the actual deployment environment

# Check if addresses follow ERC20 patterns
ast-grep --pattern 'contract $_ is ERC20 {
  $$$
}' | rg -A 5 "0x6b9bb36519538e0C073894E964E90172E1c0B41F|0xd327d36EB6E1f250D191cD62497d08b4aaa843Ce"

Length of output: 360


Script:

#!/bin/bash
# Search for files containing these addresses and show their context
rg -l "0x6b9bb36519538e0C073894E964E90172E1c0B41F|0xd327d36EB6E1f250D191cD62497d08b4aaa843Ce"

# Look for any configuration files that might contain network-specific addresses
fd -e json -e ts -e js | xargs rg "addresses|contracts" -A 5

# Check the DynamicEaterModule implementation to understand how these addresses are used
fd -e ts "DynamicEaterModule" --exec cat {}

Length of output: 98590


Script:

#!/bin/bash
# Check for any network configuration files that might contain these addresses
fd -e json -e ts "config|network" | xargs rg "0x6b9bb36519538e0C073894E964E90172E1c0B41F|0xd327d36EB6E1f250D191cD62497d08b4aaa843Ce"

# Check if these addresses are used in test files with network-specific context
rg -C 5 "network.*base|chainId.*8453|0x6b9bb36519538e0C073894E964E90172E1c0B41F|0xd327d36EB6E1f250D191cD62497d08b4aaa843Ce" test/

# Look for deployment configurations or network-specific settings
fd -e ts -e json "hardhat.config|deploy" | xargs rg "networks.*base|chainId.*8453"

Length of output: 2576

🧰 Tools
🪛 Gitleaks

5-5: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

contracts/core/merge/DynamicEater.sol (2)

74-78: ⚠️ Potential issue

Prevent potential overflow in slope calculation.

The slope calculation could overflow with large maxSupply values due to the scaling operations.

Add overflow protection:

 function slope() public view returns (int256) {
+    require(maxSupply <= type(uint256).max / 10 ** 4, "DynamicEater: maxSupply too large");
     int256 delta_x = int256(maxSupply) / 10 ** 18; // Scaling factor to 1
     int256 _slope = (maxRate - minRate) / delta_x; // Calculating slope from max and min values
     return _slope * 10 ** 4; // Scaling factor to 1_000
 }

Likely invalid or redundant comment.


211-223: ⚠️ Potential issue

Restrict receiveApproval function to the Wewe token contract.

The function should only be callable by the Wewe token contract to prevent unauthorized access.

Add a modifier to restrict access:

+modifier onlyWeweToken() {
+    require(msg.sender == wewe, "DynamicEater: Unauthorized sender");
+    _;
+}

 function receiveApproval(
     address from,
     uint256 amount,
     address,
     bytes calldata
- ) external nonReentrant whenNotPaused {
+ ) external nonReentrant whenNotPaused onlyWeweToken {
     // After wewe approve and call, it will call this function
     require(_token != address(0), "DynamicEater: Token address not set");
     _merge(amount, _token, from);
 }

Likely invalid or redundant comment.

ignition/deployments/chain-8453/journal.jsonl (2)

6-7: LGTM! Deployment completed successfully.

The contract was successfully deployed to Base chain:

  • Contract address: 0x4Fb6b659bc243a7354A6FFDD66c0bC1F1241F66c
  • Transaction hash: 0x8819d464f777a26b0bd7ee08cc0a16600c8f342f22199e7cdcbd60a5ccfb47ab

3-3: Verify the constructor arguments for the DynamicEater contract.

The constructor is initialized with:

  • Two addresses that need role verification
  • Duration of 60 (units needed)
  • Amount of 600000000000000 (decimal places needed)
✅ Verification successful

Let me analyze the results and generate additional verification scripts to better understand the contract and its parameters.

The first search helped identify that WEWE_ADDRESS is consistently used across the codebase and is listed in README.md as "WeWe" token. The second address (TOKEN) appears to be another token contract. I need to verify the contract implementation to understand the duration unit and amount calculation before providing the final response.


Let me modify the search to find the relevant information without relying on file extensions.

The previous search had issues with file type recognition. These new commands should help us understand the contract implementation, duration units, and token decimals without relying on specific file extensions.


Based on the search results, I can now provide a complete verification of the constructor arguments:

Constructor arguments for DynamicEater are correctly configured

The parameters are:

  1. WEWE_ADDRESS (0x6b9bb36519538e0C073894E964E90172E1c0B41F): Verified as WeWe token on Base chain
  2. TOKEN (0xd327d36EB6E1f250D191cD62497d08b4aaa843Ce): Target token to be "eaten"
  3. vestingDuration (60): Verified as minutes from contract implementation:
end: block.timestamp + vestingDuration * 1 minutes
  1. maxSupply (600000000000000): Correctly scaled for 9 decimals as shown in module:
const maxSupply = ethers.parseUnits("600000", 9);
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the token decimals and roles of the addresses

# Test 1: Check if the first address is a token contract
rg -A 2 "0x6b9bb36519538e0C073894E964E90172E1c0B41F"

# Test 2: Check if the second address has a specific role
rg -A 2 "0xd327d36EB6E1f250D191cD62497d08b4aaa843Ce"

# Test 3: Search for duration constants to verify the unit (days/months)
ast-grep --pattern 'const $_: $_ = 60'

Length of output: 32618


Script:

#!/bin/bash
# Check DynamicEater contract implementation to understand the duration unit and amount
ast-grep --pattern 'contract DynamicEater {
  $$$
}'

# Check for any duration-related constants or documentation
rg -A 2 "duration|period|vesting" --type ts --type sol

# Check for any token decimals information
rg -A 2 "decimals|DECIMALS" --type ts --type sol

Length of output: 218


Script:

#!/bin/bash
# Find DynamicEater contract implementation
fd -e sol . | xargs rg -A 5 "contract DynamicEater"

# Check for duration/vesting related code without file type restriction
rg -A 2 "duration|period|vesting"

# Check for decimals information without file type restriction
rg -A 2 "decimals|DECIMALS"

# Find any documentation about the contract parameters
fd "README|docs" | xargs rg -A 3 "DynamicEater|vesting"

Length of output: 66673

ignition/deployments/chain-8453/artifacts/DynamicEaterModule#DynamicEater.json (2)

1-5: LGTM! Contract structure follows best practices.

The contract is well-structured and uses standard OpenZeppelin contracts for core functionality:

  • Ownable for access control
  • ReentrancyGuard for security
  • Pausable for emergency stops

7-31: Please clarify the purpose of the _vult parameter.

While most constructor parameters are self-explanatory, the purpose of the _vult parameter is not clear from the contract interface. Consider adding documentation to explain its role.

Let me check if there's any usage pattern of this parameter:

✅ Verification successful

Based on the search results, I can now provide a clear explanation of the _vult parameter's purpose:

The _vult parameter represents the token address that users can merge into the DynamicEater contract

The evidence from the codebase shows that:

  • The _vult address is stored as _token in the constructor and is used as the source token that users can merge
  • The contract allows users to merge this token to receive wewe tokens based on a dynamic rate
  • The token is used in key operations:
    • merge() function accepts this token from users
    • mergeAndSell() function accepts and sells this token through an AMM
    • The contract tracks balances and vesting of this token
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for _vult usage patterns
rg -U "vult|_vult" -A 5

Length of output: 37669

Comment on lines +7 to +13
export default buildModule("DynamicEaterModule", m => {
const vestingPeriod = 60;
const maxSupply = ethers.parseUnits("600000", 9);
const merge = m.contract("DynamicEater", [WEWE_ADDRESS, TOKEN, vestingPeriod, maxSupply]);

return { merge };
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider adding input validation and documentation.

The module would benefit from:

  1. Input parameter validation
  2. JSDoc documentation describing the module's purpose and parameters
  3. Error handling for contract deployment failures
+/**
+ * @title DynamicEater Module
+ * @dev Deploys DynamicEater contract with vesting functionality
+ * @param WEWE_ADDRESS The address of the WEWE token
+ * @param TOKEN The address of the vesting token
+ * @param vestingPeriod The duration of the vesting period in minutes
+ * @param maxSupply The maximum token supply with 9 decimals
+ */
 export default buildModule("DynamicEaterModule", m => {
+  // Validate addresses
+  if (!ethers.isAddress(WEWE_ADDRESS) || !ethers.isAddress(TOKEN)) {
+    throw new Error("Invalid token addresses");
+  }
+
+  // Validate vesting period
+  if (vestingPeriod <= 0) {
+    throw new Error("Vesting period must be positive");
+  }
+
   const vestingPeriod = 60;
   const maxSupply = ethers.parseUnits("600000", 9);
   const merge = m.contract("DynamicEater", [WEWE_ADDRESS, TOKEN, vestingPeriod, maxSupply]);

Committable suggestion was skipped due to low confidence.

Comment on lines +28 to +42
function sell(
uint256 amount,
address token,
address recipient,
bytes calldata extraData
) external returns (uint256) {
uint24 feeTokenWeth = 10000;
uint24 feeWethToUsdc = 500;
uint24 feeUsdcToWewe = 10000;
bytes memory path = abi.encodePacked(token, feeTokenWeth, wrappedETH, feeWethToUsdc, USDC, feeUsdcToWewe, wewe);
uint256 amountOut = _swapMultihop(token, amount, path, msg.sender, recipient, 0);

emit Sold(amount, amountOut, token, recipient);
return amountOut;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

⚠️ Potential issue

Now I have enough context to generate the final response since I can see that:

  1. The wewe token address is defined in the base contract BaseUniRouter as a constant: 0x6b9bb36519538e0C073894E964E90172E1c0B41F
  2. The contract inherits from BaseUniRouter and implements IAMM interface
  3. The base contract already has fee validation in place

Security improvements needed, but review needs adjustment

The review comment's concerns about input validation and slippage protection are valid, but some modifications are needed:

  1. The wewe token concern can be dismissed as it's properly defined in the base contract
  2. The hardcoded fee tiers concern can be removed as there's already fee validation in the base contract
  3. The remaining security improvements are valid and should be implemented:
    • Add zero-address validation for token and recipient
    • Add amount validation
    • Add slippage protection using extraData

Recommended changes:

 function sell(
     uint256 amount,
     address token,
     address recipient,
     bytes calldata extraData
 ) external returns (uint256) {
+    require(token != address(0), "Invalid token");
+    require(recipient != address(0), "Invalid recipient");
+    require(amount > 0, "Invalid amount");
+    
+    // Extract minimum output amount from extraData
+    uint256 minAmountOut = abi.decode(extraData, (uint256));
+    require(minAmountOut > 0, "Invalid minAmountOut");
+
     uint24 feeTokenWeth = 10000;
     uint24 feeWethToUsdc = 500;
     uint24 feeUsdcToWewe = 10000;
     bytes memory path = abi.encodePacked(token, feeTokenWeth, wrappedETH, feeWethToUsdc, USDC, feeUsdcToWewe, wewe);
-    uint256 amountOut = _swapMultihop(token, amount, path, msg.sender, recipient, 0);
+    uint256 amountOut = _swapMultihop(token, amount, path, msg.sender, recipient, minAmountOut);
 
     emit Sold(amount, amountOut, token, recipient);
     return amountOut;
 }
🔗 Analysis chain

Add input validation and slippage protection.

Several security concerns need to be addressed:

  1. Missing input validation for token and recipient addresses
  2. No validation for amount
  3. Zero slippage protection (amountOutMinimum = 0) which could lead to sandwich attacks
  4. Hardcoded fee tiers might not be optimal for all token pairs

Apply these security improvements:

 function sell(
     uint256 amount,
     address token,
     address recipient,
     bytes calldata extraData
 ) external returns (uint256) {
+    require(token != address(0), "Invalid token");
+    require(recipient != address(0), "Invalid recipient");
+    require(amount > 0, "Invalid amount");
+
     uint24 feeTokenWeth = 10000;
     uint24 feeWethToUsdc = 500;
     uint24 feeUsdcToWewe = 10000;
     bytes memory path = abi.encodePacked(token, feeTokenWeth, wrappedETH, feeWethToUsdc, USDC, feeUsdcToWewe, wewe);
-    uint256 amountOut = _swapMultihop(token, amount, path, msg.sender, recipient, 0);
+    
+    // Extract minimum output amount from extraData
+    uint256 minAmountOut = abi.decode(extraData, (uint256));
+    require(minAmountOut > 0, "Invalid minAmountOut");
+    
+    uint256 amountOut = _swapMultihop(token, amount, path, msg.sender, recipient, minAmountOut);
 
     emit Sold(amount, amountOut, token, recipient);
     return amountOut;
 }

Let me try a different approach to verify the codebase.

The previous search failed because of file type issues. Let's try these broader searches to understand the contract structure and wewe token definition. This will help us verify the security concerns and validate the review comment's suggestions.


Let me check the base contract and inheritance to understand the complete context.

This will help us understand where wewe is defined and verify the contract's inheritance structure to make a proper assessment of the security concerns.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Verify the wewe token address definition
rg -l "wewe\s*=.*" --type sol

Length of output: 59


Script:

#!/bin/bash
# Search for wewe token definition and usage
rg -l "wewe"

# Search for the base contract that might define wewe
rg -A 5 "contract UniswapV3ViaRouterFomo"

# Look for inheritance or state variables
ast-grep --pattern 'contract UniswapV3ViaRouterFomo $_'

Length of output: 1588


Script:

#!/bin/bash
# Check BaseUniRouter for wewe definition
rg -A 10 "contract BaseUniRouter" contracts/core/adaptors/BaseUniRouter.sol

# Check IAMM interface
rg -A 10 "interface IAMM"

# Look for wewe address definition in the contract
rg "wewe\s*=|wewe.*constant" contracts/core/adaptors/UniswapV3ViaRouterFomo.sol

Length of output: 3472

Comment on lines +38 to +63
// // Working with small value
// it.only("Should decrease deposit rates", async () => {
// const { merge, token, wewe, otherAccount } = await loadFixture(deployFixture);

// const mergeAddress = await merge.getAddress();

// let rate = await merge.getRate();
// expect(rate).to.be.eq(12000000000n); // Starting rate should be 120%

// await merge.deposit(1000n);

// let totalVested = await merge.totalVested();
// expect(totalVested).to.be.eq(0);

// const slope = await merge.slope();
// expect(slope).to.be.eq(116666666666n);

// const reward = await merge.getTotalWeWe(100n);
// expect(reward).to.be.eq(120000116666666666n);

// // // Merge 100 vult to wewe
// // await merge.connect(otherAccount).merge(100n);
// // const vested = await merge.vestings(otherAccount.address);

// // expect(vested.amount).to.be.eq(50n);
// });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove or implement commented test case.

The commented test case contains valuable test scenarios for rate calculation and vesting verification. Either remove it or implement it properly.

Would you like me to help implement this test case properly?

Comment on lines +37 to +148
// let totalVested = await merge.totalVested();
// expect(totalVested).to.be.eq(0);

// const slope = await merge.slope();
// expect(slope).to.be.eq(116666666666n);

// const reward = await merge.getTotalWeWe(100n);
// expect(reward).to.be.eq(120000116666666666n);

// // // Merge 100 vult to wewe
// // await merge.connect(otherAccount).merge(100n);
// // const vested = await merge.vestings(otherAccount.address);

// // expect(vested.amount).to.be.eq(50n);
// });

it.skip("Should calcuate rates with small numbers", async () => {
const { merge, token, wewe, otherAccount } = await loadFixture(deployFixture);

const mergeAddress = await merge.getAddress();

// Deposit wewe to setup the merge
await merge.deposit(WE_WE_TO_DEPOSIT);

// let scalar = await merge.getScalar(1000);
// // expect(reward).to.be.eq(120);
// expect(scalar).to.be.eq(11989);

// scalar = await merge.getScalar(10000);
// expect(scalar).to.be.approximately(11900, 100);

// // reward = await merge.getTotalWeWe(30000);
// // expect(reward).to.be.eq(117);

// // reward = await merge.getTotalWeWe(100000);
// // expect(reward).to.be.eq(109);

// scalar = await merge.getScalar(500000);
// expect(scalar).to.be.approximately(6200, 100);

// let weweRecieved = await merge.getTotalWeWe(1000);
// expect(weweRecieved).to.be.approximately(1570, 10);

// weweRecieved = await merge.getTotalWeWe(500000);
// expect(weweRecieved).to.be.approximately(403938, 10);

let totalVested = await merge.totalVested();
expect(totalVested).to.be.eq(0);

let weweBalance = await wewe.balanceOf(otherAccount.address);
expect(weweBalance).to.be.eq(0);

let tokenBalance = await token.balanceOf(otherAccount.address);
expect(tokenBalance).to.be.eq(100000);

// Merge 100,000 vult to wewe
await merge.connect(otherAccount).merge(100000);
// expect(await merge.totalVested()).to.be.eq(100000);

// not yet sent
weweBalance = await wewe.balanceOf(otherAccount.address);
expect(weweBalance).to.be.eq(0);

tokenBalance = await token.balanceOf(otherAccount.address);
expect(tokenBalance).to.be.eq(0);

// Get the vested amount
const vested = await merge.vestings(otherAccount.address);
expect(vested.amount).to.be.eq(141925);
});

it.only("Should calcuate rates with large numbers", async () => {
const { merge, otherAccount } = await loadFixture(deployFixture);

// Deposit wewe to setup the merge
await merge.deposit(ethers.parseEther("1000000"));

// let scalar = await merge.getScalar(ethers.parseEther("1000"));
// expect(scalar).to.be.eq(11989);

// let weweRecieved = await merge.getTotalWeWe(ethers.parseEther("1000"));
// // console.log(weweRecieved.toString());
// // expect(weweRecieved).to.be.approximately(1570, 10);

await merge.connect(otherAccount).merge(ethers.parseEther("100000"));

let vested = await merge.vestings(otherAccount.address);
console.log(vested.amount.toString());

await merge.connect(otherAccount).merge(ethers.parseEther("100000"));

vested = await merge.vestings(otherAccount.address);
console.log(vested.amount.toString());

await merge.connect(otherAccount).merge(ethers.parseEther("100000"));

vested = await merge.vestings(otherAccount.address);
console.log(vested.amount.toString());
});
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add test coverage for edge cases.

The test suite is missing important edge cases for the dynamic rate mechanism:

  1. Maximum supply reached
  2. Zero amount merges
  3. Minimum rate scenarios

Add these test cases:

it("Should revert when max supply is reached", async () => {
  const { merge, otherAccount } = await loadFixture(deployFixture);
  await merge.deposit(WE_WE_TO_DEPOSIT);
  
  // Attempt to merge more than max supply
  await expect(
    merge.connect(otherAccount).merge(ethers.parseEther("700000"))
  ).to.be.revertedWith("Max supply exceeded");
});

it("Should revert on zero amount merge", async () => {
  const { merge, otherAccount } = await loadFixture(deployFixture);
  await merge.deposit(WE_WE_TO_DEPOSIT);
  
  await expect(
    merge.connect(otherAccount).merge(0)
  ).to.be.revertedWith("Amount must be greater than 0");
});

it("Should handle minimum rate correctly", async () => {
  const { merge, otherAccount } = await loadFixture(deployFixture);
  await merge.deposit(WE_WE_TO_DEPOSIT);
  
  // Perform multiple merges to reach minimum rate
  for(let i = 0; i < 5; i++) {
    await merge.connect(otherAccount).merge(ethers.parseEther("100000"));
  }
  
  const rate = await merge.getRate();
  expect(rate).to.be.gte(await merge.MIN_RATE());
});
🧰 Tools
🪛 Biome

[error] 120-120: Don't focus the test.

The 'only' method is often used for debugging or during implementation. It should be removed before deploying to production.
Consider removing 'only' to ensure all tests are executed.
Unsafe fix: Remove focus from test.

(lint/suspicious/noFocusedTests)

Comment on lines +65 to +118
it.skip("Should calcuate rates with small numbers", async () => {
const { merge, token, wewe, otherAccount } = await loadFixture(deployFixture);

const mergeAddress = await merge.getAddress();

// Deposit wewe to setup the merge
await merge.deposit(WE_WE_TO_DEPOSIT);

// let scalar = await merge.getScalar(1000);
// // expect(reward).to.be.eq(120);
// expect(scalar).to.be.eq(11989);

// scalar = await merge.getScalar(10000);
// expect(scalar).to.be.approximately(11900, 100);

// // reward = await merge.getTotalWeWe(30000);
// // expect(reward).to.be.eq(117);

// // reward = await merge.getTotalWeWe(100000);
// // expect(reward).to.be.eq(109);

// scalar = await merge.getScalar(500000);
// expect(scalar).to.be.approximately(6200, 100);

// let weweRecieved = await merge.getTotalWeWe(1000);
// expect(weweRecieved).to.be.approximately(1570, 10);

// weweRecieved = await merge.getTotalWeWe(500000);
// expect(weweRecieved).to.be.approximately(403938, 10);

let totalVested = await merge.totalVested();
expect(totalVested).to.be.eq(0);

let weweBalance = await wewe.balanceOf(otherAccount.address);
expect(weweBalance).to.be.eq(0);

let tokenBalance = await token.balanceOf(otherAccount.address);
expect(tokenBalance).to.be.eq(100000);

// Merge 100,000 vult to wewe
await merge.connect(otherAccount).merge(100000);
// expect(await merge.totalVested()).to.be.eq(100000);

// not yet sent
weweBalance = await wewe.balanceOf(otherAccount.address);
expect(weweBalance).to.be.eq(0);

tokenBalance = await token.balanceOf(otherAccount.address);
expect(tokenBalance).to.be.eq(0);

// Get the vested amount
const vested = await merge.vestings(otherAccount.address);
expect(vested.amount).to.be.eq(141925);
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix skipped test case implementation.

The test case has several issues:

  1. Uses .skip modifier
  2. Contains commented out assertions
  3. Missing validation for vesting end timestamp

Apply these changes:

-it.skip("Should calcuate rates with small numbers", async () => {
+it("Should calculate rates with small numbers", async () => {
   const { merge } = await loadFixture(deployFixture);
   
   // Deposit wewe to setup the merge
   await merge.deposit(WE_WE_TO_DEPOSIT);

-  // let scalar = await merge.getScalar(1000);
-  // expect(scalar).to.be.eq(11989);
+  let scalar = await merge.getScalar(1000);
+  expect(scalar).to.be.eq(11989);

   // Get the vested amount
   const vested = await merge.vestings(otherAccount.address);
   expect(vested.amount).to.be.eq(141925);
+  const blockTimestamp = (await ethers.provider.getBlock('latest')).timestamp;
+  expect(vested.end).to.be.eq(blockTimestamp + vestingPeriod);

Committable suggestion was skipped due to low confidence.

Comment on lines +242 to +250
modifier whenClaimable(address account) {
// Set to 0 to disable vesting
if (vestingDuration == 0) {
_;
}

require(vestings[account].end <= block.timestamp, "DynamicEater: Vesting not ended");
_;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix the double invocation issue in whenClaimable modifier.

The modifier contains two instances of _;, which will cause the function body to execute twice when vestingDuration == 0.

Apply this fix:

 modifier whenClaimable(address account) {
-    // Set to 0 to disable vesting
-    if (vestingDuration == 0) {
-        _;
-    }
-
-    require(vestings[account].end <= block.timestamp, "DynamicEater: Vesting not ended");
+    // If vesting is enabled, check if vesting has ended
+    if (vestingDuration != 0) {
+        require(vestings[account].end <= block.timestamp, "DynamicEater: Vesting not ended");
+    }
     _;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
modifier whenClaimable(address account) {
// Set to 0 to disable vesting
if (vestingDuration == 0) {
_;
}
require(vestings[account].end <= block.timestamp, "DynamicEater: Vesting not ended");
_;
}
modifier whenClaimable(address account) {
// If vesting is enabled, check if vesting has ended
if (vestingDuration != 0) {
require(vestings[account].end <= block.timestamp, "DynamicEater: Vesting not ended");
}
_;
}

Comment on lines +252 to +259
modifier whenSolvent(uint256 amountToMerge) {
uint256 newAmountToVest = (amountToMerge * _getCurrentRate(amountToMerge)) / 100_000;
require(
IERC20(wewe).balanceOf(address(this)) >= _totalVested + newAmountToVest,
"DynamicEater: Insufficient Wewe balance"
);
_;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add overflow protection in whenSolvent modifier.

The calculation of newAmountToVest could potentially overflow with large values of amountToMerge.

Add SafeMath or unchecked block based on your requirements:

 modifier whenSolvent(uint256 amountToMerge) {
+    require(amountToMerge <= type(uint256).max / _getCurrentRate(amountToMerge), "DynamicEater: Amount too large");
     uint256 newAmountToVest = (amountToMerge * _getCurrentRate(amountToMerge)) / 100_000;
     require(
         IERC20(wewe).balanceOf(address(this)) >= _totalVested + newAmountToVest,
         "DynamicEater: Insufficient Wewe balance"
     );
     _;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
modifier whenSolvent(uint256 amountToMerge) {
uint256 newAmountToVest = (amountToMerge * _getCurrentRate(amountToMerge)) / 100_000;
require(
IERC20(wewe).balanceOf(address(this)) >= _totalVested + newAmountToVest,
"DynamicEater: Insufficient Wewe balance"
);
_;
}
modifier whenSolvent(uint256 amountToMerge) {
require(amountToMerge <= type(uint256).max / _getCurrentRate(amountToMerge), "DynamicEater: Amount too large");
uint256 newAmountToVest = (amountToMerge * _getCurrentRate(amountToMerge)) / 100_000;
require(
IERC20(wewe).balanceOf(address(this)) >= _totalVested + newAmountToVest,
"DynamicEater: Insufficient Wewe balance"
);
_;
}

Comment on lines +167 to +179
function mergeAndSell(uint256 amount, IAMM amm, bytes calldata extraData) external nonReentrant whenNotPaused {
uint256 balance = IERC20(_token).balanceOf(msg.sender);
require(balance >= amount, "DynamicEater: Insufficient balance to eat");

_merge(amount, _token, msg.sender);

// Approve the AMM to use the tokens now in this contract
IERC20(_token).approve(address(amm), amount);

// Sell the tokens, can fund the contract with the token
address recipient = treasury == address(0) ? address(this) : treasury;
amm.sell(amount, _token, recipient, extraData);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add slippage protection in mergeAndSell.

The function lacks slippage protection when selling tokens through the AMM, which could lead to unfavorable trades.

Consider adding minimum output amount parameter and validation:

 function mergeAndSell(
     uint256 amount,
     IAMM amm,
+    uint256 minOutput,
     bytes calldata extraData
 ) external nonReentrant whenNotPaused {
     // ... existing code ...
     
     // Sell the tokens with slippage protection
     address recipient = treasury == address(0) ? address(this) : treasury;
-    amm.sell(amount, _token, recipient, extraData);
+    uint256 outputAmount = amm.sell(amount, _token, recipient, extraData);
+    require(outputAmount >= minOutput, "DynamicEater: Insufficient output amount");
 }

Committable suggestion was skipped due to low confidence.

Comment on lines +134 to +165
function _merge(uint256 amount, address token, address from) internal {
// uint256 weweToTransfer = getTotalWeWe(_totalVested + amount, 0);
uint256 y1 = 0;
if (_totalVested > 0) y1 = (_totalVested / 8571) * 10 ** 9; // come down the pervious value to the y axis intercept

uint256 weweToTransfer = getTotalWeWe(amount, y1);

require(
weweToTransfer <= IERC20(wewe).balanceOf(address(this)),
"DynamicEater: Insufficient token balance to transfer"
);

// Merge tokens from sender
IERC20(token).transferFrom(from, address(this), amount);

// If transfer, dont vest
if (vestingDuration != 0) {
// Curent vested
uint256 vestedAmount = vestings[from].amount;
vestings[from] = Vesting({
amount: weweToTransfer + vestedAmount,
end: block.timestamp + vestingDuration * 1 minutes
});

_totalVested += weweToTransfer;
} else {
// Transfer Wewe tokens to sender
IERC20(wewe).transfer(from, weweToTransfer);
}

emit Merged(amount, from);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Use SafeERC20 for token transfers and emit vesting events.

The _merge function has potential security and transparency issues:

  1. Direct use of transferFrom can be unsafe with non-compliant tokens
  2. Vesting updates should emit events for transparency

Apply these security improvements:

+import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
+
+using SafeERC20 for IERC20;
+
+event VestingUpdated(address indexed account, uint256 amount, uint256 end);

 function _merge(uint256 amount, address token, address from) internal {
     // ... existing code ...
-    IERC20(token).transferFrom(from, address(this), amount);
+    IERC20(token).safeTransferFrom(from, address(this), amount);
     
     if (vestingDuration != 0) {
         vestings[from] = Vesting({
             amount: weweToTransfer + vestedAmount,
             end: block.timestamp + vestingDuration * 1 minutes
         });
+        emit VestingUpdated(from, weweToTransfer + vestedAmount, block.timestamp + vestingDuration * 1 minutes);
     }
 }

Committable suggestion was skipped due to low confidence.

Comment on lines +32 to +108
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"indexed": true,
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "Merged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "previousOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnershipTransferred",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "Paused",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "newRate",
"type": "uint256"
}
],
"name": "RateChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "Unpaused",
"type": "event"
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider adding vesting-related events.

While the contract has good event coverage for most actions, it's missing events for vesting-related state changes. Consider adding:

  • VestingCreated(address indexed account, uint256 amount, uint256 end)
  • VestingClaimed(address indexed account, uint256 amount)

This would improve transparency and make it easier to track vesting activities off-chain.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

vult merge v2
1 participant