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

EscrowView contract and payments refactor #102

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

jaybuidl
Copy link
Member

@jaybuidl jaybuidl commented Jan 30, 2025

PR-Codex overview

This PR introduces a new EscrowView contract that provides a view for payout messages related to transactions in the EscrowUniversal contract. It also refines the payout logic in EscrowUniversal to streamline the execution of rulings.

Detailed summary

  • Added EscrowView contract with a constructor accepting an EscrowUniversal address.
  • Implemented getPayoutMessages function to retrieve payout messages based on transaction ID.
  • Created formatEth and formatToken functions for formatting amounts.
  • Refactored executeRuling in EscrowUniversal to utilize new payout logic.
  • Introduced getPayouts function in EscrowUniversal for calculating payouts based on the winning party.

✨ Ask PR-Codex anything about this PR by commenting with /codex {your question}

Summary by CodeRabbit

  • New Features

    • Added a new view contract to provide detailed payout messages for escrow transactions.
    • Enhanced dispute resolution functionality with improved payout calculation logic.
    • Introduced advanced formatting for displaying transaction amounts in ETH and tokens.
    • Added a new task for testing payout messages associated with escrow transactions.
  • Improvements

    • Streamlined payout processing in the escrow contract.
    • Added more robust handling of transaction payouts during dispute resolution.
    • Updated package manager and dependencies for better performance and compatibility.
    • Updated deployed contract addresses for EscrowUniversal and added EscrowView.

Copy link
Contributor

coderabbitai bot commented Jan 30, 2025

Walkthrough

The pull request introduces significant modifications to the escrow contract system. The EscrowUniversal contract is refactored to enhance payout logic, with a new getPayouts function that provides a more modular approach to handling dispute resolutions. Additionally, a new EscrowView contract is added to provide human-readable payout messages and formatting for transaction details, improving the system's clarity and user interaction.

Changes

File Change Summary
contracts/src/EscrowUniversal.sol - Introduced getPayouts function for structured payout calculations
- Refactored executeRuling to utilize new payout logic
contracts/src/EscrowView.sol - New contract for viewing escrow transaction details
- Added getPayoutMessages to retrieve dispute resolution messages
- Implemented formatEth and formatToken for amount formatting
contracts/deploy/00-escrow.ts - Updated deployment script to include EscrowView and changed how EscrowUniversal is accessed
contracts/deployments/arbitrum/.chainId - Added chain ID 42161 for Arbitrum network
contracts/deployments/arbitrum/DAI.json - Introduced DAI contract deployment details including address and ABI
contracts/deployments/arbitrum/WETH.json - Introduced WETH contract deployment details including address and ABI
contracts/hardhat.config.ts - Updated account management and network configurations
contracts/package.json - Updated node version and various dependencies
contracts/scripts/getPayoutMessages.ts - Added Hardhat task testPayoutMessages for testing payout messages

Possibly related PRs

  • Absolute tx deadline #60: This PR modifies the EscrowUniversal contract, specifically updating the createNativeTransaction and createERC20Transaction functions, which are relevant to the changes made in the main PR regarding the executeRuling and getPayouts functions.
  • Improve pay/reimburse/execute flow #64: This PR updates the EscrowUniversal contract by adding event emissions and modifying transaction handling, which relates to the changes in payout logic and transaction management in the main PR.
  • feat(Escrow): cap the amount #88: This PR introduces a new mapping and modifier in the EscrowUniversal contract to cap transaction amounts, which is relevant to the overall transaction handling and payout logic discussed in the main PR.

Suggested labels

Type: Enhancement :sparkles:, Package: Web, Package: Contracts, Compatibility: ABI change 🗯, Type: Feature🗿

Poem

🐰 In the realm of escrow, where disputes arise,
A rabbit hops through code with clever eyes.
Payouts now clear, no more complex maze,
With getPayouts and messages that amaze.
Contracts dance, justice finds its prize! 🏆


🪧 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 generate docstrings to generate docstrings for this PR. (Beta)
  • @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 or @coderabbitai title 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.

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

🧹 Nitpick comments (6)
contracts/src/EscrowUniversal.sol (3)

474-477: Check _ruling to Party casting logic for out-of-range values.
Casting _ruling to Party may introduce unexpected results if _ruling is outside recognized enum values. Ensure the call site or prior checks safeguard _ruling from invalid values.


486-491: Consider reentrancy protection or explicit error handling.
While sending ETH to the addresses is a valid approach, reentrancy concerns can arise. Consider documenting that this contract intentionally uses .send to avoid blocking in fallback functions, or add a reentrancy guard if that risk is not acceptable.


511-576: Ensure business logic matches the documentation.
The getPayouts function encapsulates ruling-based payouts correctly and matches the comment descriptions. Carefully verify that partial settlement amounts are honored correctly (e.g. if settlementSeller is zero, default to the full amount).

contracts/src/EscrowView.sol (3)

16-20: Clarify post-dispute payoff logic.
The doc comments are helpful, but you could emphasize that payouts remain subject to final arbitration fees. Consider highlighting that these values may be approximations if arbitration fees are not yet paid in full.


21-29: Leverage struct retrieval for better clarity.
When accessing escrow.transactions(_transactionID), consider using a struct or named variables to reduce indexing. This can help avoid confusion about parameter order and future changes to the Transaction struct.


111-158: Formatting logic for tokens is consistent, but verify trailing zero approach.
If the resulting decimal portion is fully zero, you correctly revert to whole tokens plus symbol. This is good, but ensure you confirm stakeholder preferences for removing trailing zeros (some might prefer consistent decimal presentation).

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 08a5201 and ea3ac2d.

📒 Files selected for processing (2)
  • contracts/src/EscrowUniversal.sol (4 hunks)
  • contracts/src/EscrowView.sol (1 hunks)
🔇 Additional comments (7)
contracts/src/EscrowUniversal.sol (4)

13-13: Import usage confirmed.
The newly added import of IERC20Metadata is appropriate for retrieving token decimals and symbol.


471-473: Avoid using .send unless accepting reentrancy is intentional.
While these assignments look fine, be aware that using buyer.send(...) and seller.send(...) can lead to silent failures if the transfer is declined or runs out of gas. Consider using .transfer or .call{value: ...} with appropriate reentrancy protection if you require guaranteed success or automatically reverting on failure.


492-496: Confirm token reverts on failure.
Using safeTransfer is best practice for ERC20 tokens to revert on transfer failure. This logic is correct and consistent with the contract’s approach to handle tokens that follow ERC20’s standard.


510-510: No issues with getTransactionCount.
Returning transactions.length is straightforward and correct.

contracts/src/EscrowView.sol (3)

9-14: Constructor usage validated.
The immutable escrow reference is correctly set in the constructor, ensuring secure linking to the EscrowUniversal contract.


30-50: ETH payout messages code.
The string concatenations are logically correct. Verify any potential confusion around who shoulders arbitration costs in each scenario to ensure the display text aligns with actual logic in EscrowUniversal.


51-70: ERC20 payout messages code.
This mirrors the ETH version for non-native tokens. Looks consistent.

Comment on lines +73 to +109
function formatEth(uint256 _amountWei) public pure returns (string memory) {
uint256 ethWhole = _amountWei / 1 ether;
uint256 ethFraction = (_amountWei % 1 ether) / 1e15; // Get the first 3 decimal digits

// Convert the whole and fractional parts to strings
string memory ethWholeStr = Strings.toString(ethWhole);

// If the fractional part is zero, return only the whole part
if (ethFraction == 0) {
return string.concat(ethWholeStr, " ETH");
}

// Convert the fractional part to string with leading zeros if necessary
string memory ethFractionStr = Strings.toString(ethFraction);

// Pad the fractional part with leading zeros to ensure three digits
while (bytes(ethFractionStr).length < 3) {
ethFractionStr = string.concat("0", ethFractionStr);
}

// Remove trailing zeros from the fractional part
bytes memory fractionBytes = bytes(ethFractionStr);
uint256 fractionLength = fractionBytes.length;
while (fractionLength > 0 && fractionBytes[fractionLength - 1] == "0") {
fractionLength--;
}

if (fractionLength == 0) {
return ethWholeStr;
} else {
bytes memory fractionTrimmed = new bytes(fractionLength);
for (uint256 i = 0; i < fractionLength; i++) {
fractionTrimmed[i] = fractionBytes[i];
}
return string.concat(ethWholeStr, ".", string(fractionTrimmed), " ETH");
}
}
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

Bug: missing “ ETH” for zero-fraction scenarios that become zero after trimming.
When the fractional part is not zero initially but ends up fully trimmed, line 101 returns ethWholeStr without appending “ ETH,” conflicting with the behavior in line 82. Add “ ETH” to ensure consistent units.

Here is a suggested fix:

...
            ...
        }
...

Committable suggestion skipped: line range outside the PR's diff.

@jaybuidl jaybuidl force-pushed the feat/payouts-refactor branch from ea3ac2d to 53e8a88 Compare January 30, 2025 23:36
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: 3

🧹 Nitpick comments (3)
contracts/src/EscrowUniversal.sol (1)

533-546: Add explicit comments for settlement amount logic.

The settlement amount logic in both buyer and seller winning scenarios could benefit from more explicit comments explaining the rationale behind the calculations.

Add detailed comments explaining the settlement logic:

 if (_winningParty == Party.Buyer) {
-    // The Seller gets the settlement amount proposed by the Buyer if any, otherwise nothing.
-    // The Buyer gets the remaining amount of the transaction back if any.
-    // The Buyer gets the arbitration fee back.
+    // When buyer wins:
+    // 1. Seller receives the settlement amount that the buyer proposed (if any)
+    //    This represents a "fair" amount the buyer was willing to pay
+    // 2. Buyer receives:
+    //    - Their arbitration fee back (they won the dispute)
+    //    - The remaining transaction amount (total - settlement)
     uint256 settledAmount = settlementBuyer;
     if (nativePayment) {
 } else if (_winningParty == Party.Seller) {
-    // The Seller gets his proposed settlement amount if any, otherwise the transaction amount.
-    // The Buyer gets the remaining amount of the transaction back if any.
-    // The Seller gets the arbitration fee back.
+    // When seller wins:
+    // 1. Seller receives:
+    //    - Their proposed settlement amount (if they made a proposal)
+    //    - Otherwise, the full transaction amount
+    //    - Their arbitration fee back (they won the dispute)
+    // 2. Buyer receives any remaining amount if seller's settlement was less than total
     uint256 settledAmount = settlementSeller != 0 ? settlementSeller : amount;
     if (nativePayment) {

Also applies to: 546-559

contracts/src/EscrowView.sol (2)

30-70: Optimize gas usage and improve message consistency.

  1. Consider extracting repeated string literals to constants to save gas.
  2. The message wording is inconsistent between ETH and token branches (e.g., "pays for arbitration" placement).
+    // Constants for repeated strings
+    string private constant BACK_AND_PAY = " back and pay half of the arbitration fees each.";
+    string private constant INSTEAD_OF = " instead of ";
+    string private constant BUYER_PAYS_ARBITRATION = ", Buyer pays for arbitration.";
+    string private constant SELLER_PAYS_ARBITRATION = ", Seller pays for arbitration.";

     if (token == NATIVE) {
         noWinner = string.concat(
             "Buyer and Seller get ",
             formatEth(noWinnerPayout),
-            " back and pay half of the arbitration fees each."
+            BACK_AND_PAY
         );
         // ... similar changes for other messages
     }

1-159: Consider architectural improvements for better maintainability and gas efficiency.

  1. Move string constants and formatting logic to a separate library to improve maintainability and reusability.
  2. Consider implementing a caching mechanism for frequently accessed token metadata to reduce external calls.

Would you like me to provide an example implementation of these improvements?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ea3ac2d and 53e8a88.

📒 Files selected for processing (2)
  • contracts/src/EscrowUniversal.sol (3 hunks)
  • contracts/src/EscrowView.sol (1 hunks)
🔇 Additional comments (4)
contracts/src/EscrowUniversal.sol (2)

510-575: LGTM! Well-structured payout calculation logic.

The getPayouts function is well-designed with:

  • Clear separation of native and token payment handling
  • Comprehensive coverage of all winning scenarios
  • Good handling of settlement amounts
  • Proper implementation of the split logic for draws

563-566: Handle potential rounding losses in split calculations.

When splitting amounts, any odd number will result in rounding down, effectively burning one unit of currency/token. This should be documented and potentially handled differently.

Consider adding explicit handling for the remainder:

-    uint256 splitArbitrationFee = buyerFee / 2; // buyerFee equals sellerFee.
-    buyerPayout = splitArbitrationFee;
-    sellerPayout = splitArbitrationFee;
-    uint256 splitAmount = amount / 2;
+    uint256 splitArbitrationFee = buyerFee / 2;
+    uint256 arbitrationFeeRemainder = buyerFee % 2;
+    buyerPayout = splitArbitrationFee + arbitrationFeeRemainder; // Give remainder to buyer
+    sellerPayout = splitArbitrationFee;
+    uint256 splitAmount = amount / 2;
+    uint256 amountRemainder = amount % 2;
+    if (nativePayment) {
+        buyerPayout += splitAmount + amountRemainder; // Give remainder to buyer
+        sellerPayout += splitAmount;
+    } else {
+        buyerPayoutToken = splitAmount + amountRemainder; // Give remainder to buyer
+        sellerPayoutToken = splitAmount;
+    }
contracts/src/EscrowView.sol (2)

1-14: Consider updating to the latest Solidity version.

The contract is using Solidity 0.8.18, while the latest version is 0.8.24. Consider updating to benefit from the latest security fixes and features.

Otherwise, the contract setup looks good with appropriate imports and immutable state variable usage.


73-109: Fix ETH formatting issues and optimize gas usage.

  1. The bug where " ETH" suffix is missing for zero-fraction scenarios is still present.

  2. The string operations could be optimized for gas efficiency:

-        while (bytes(ethFractionStr).length < 3) {
-            ethFractionStr = string.concat("0", ethFractionStr);
+        bytes memory fractionBytes = bytes(ethFractionStr);
+        bytes memory paddedFraction = new bytes(3);
+        uint256 padLength = 3 - fractionBytes.length;
+        for (uint256 i = 0; i < padLength; i++) {
+            paddedFraction[i] = bytes1("0");
+        }
+        for (uint256 i = 0; i < fractionBytes.length; i++) {
+            paddedFraction[i + padLength] = fractionBytes[i];
         }
+        ethFractionStr = string(paddedFraction);
  1. Add validation for potential overflow:
     function formatEth(uint256 _amountWei) public pure returns (string memory) {
+        require(_amountWei <= type(uint256).max, "EscrowView: Amount too large");
         uint256 ethWhole = _amountWei / 1 ether;

Comment on lines +485 to +487
if (buyerPayout > 0) {
buyer.send(buyerPayout); // It is the user responsibility to accept ETH.
}
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

Consider using transfer() instead of send() for native currency transfers.

The send() function is used for native currency transfers but its return value is not checked. This could silently fail without reverting the transaction.

Consider using transfer() instead, which automatically reverts on failure:

-    buyer.send(buyerPayout); // It is the user responsibility to accept ETH.
+    (bool success, ) = buyer.call{value: buyerPayout}("");
+    require(success, "Native transfer to buyer failed");
-    seller.send(sellerPayout); // It is the user responsibility to accept ETH.
+    (bool success, ) = seller.call{value: sellerPayout}("");
+    require(success, "Native transfer to seller failed");

Also applies to: 488-490

Comment on lines 16 to 22
/// @notice Get the payout messages for a transaction once a dispute is created.
/// @notice The amounts pre-dispute are imprecise as the arbitration fees are not paid yet by either or both parties.
function getPayoutMessages(
uint256 _transactionID
) external view returns (string memory noWinner, string memory buyerWins, string memory sellerWins) {
(, , uint256 amount, , , , , , , , , IERC20 token) = escrow.transactions(_transactionID);

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 and error handling.

The function should validate the transaction ID and handle non-existent transactions. Consider adding a check to verify that the transaction exists and is in a valid state.

 function getPayoutMessages(
     uint256 _transactionID
 ) external view returns (string memory noWinner, string memory buyerWins, string memory sellerWins) {
-    (, , uint256 amount, , , , , , , , , IERC20 token) = escrow.transactions(_transactionID);
+    (,, uint256 amount,,,,, Status status,,,, IERC20 token) = escrow.transactions(_transactionID);
+    require(status != Status.NoDispute, "EscrowView: No dispute created yet");

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 111 to 158
function formatToken(uint256 _amountWei, address _token) public view returns (string memory) {
IERC20Metadata token = IERC20Metadata(_token);
uint8 decimals = token.decimals();
string memory symbol = token.symbol();

uint256 tenToDecimals = uint256(10) ** uint256(decimals);
uint256 tokenWhole = _amountWei / tenToDecimals;

uint256 tokenFraction;
uint8 fractionDigits;

if (decimals >= 3) {
uint256 divider = uint256(10) ** uint256(decimals - 3);
tokenFraction = (_amountWei % tenToDecimals) / divider;
fractionDigits = 3;
} else {
tokenFraction = _amountWei % tenToDecimals;
fractionDigits = decimals;
}

string memory tokenWholeStr = Strings.toString(tokenWhole);

if (tokenFraction == 0) {
return string.concat(tokenWholeStr, " ", symbol);
}

string memory tokenFractionStr = Strings.toString(tokenFraction);

while (bytes(tokenFractionStr).length < fractionDigits) {
tokenFractionStr = string.concat("0", tokenFractionStr);
}

bytes memory fractionBytes = bytes(tokenFractionStr);
uint256 fractionLength = fractionBytes.length;
while (fractionLength > 0 && fractionBytes[fractionLength - 1] == bytes1("0")) {
fractionLength--;
}

if (fractionLength == 0) {
return string.concat(tokenWholeStr, " ", symbol);
} else {
bytes memory fractionTrimmed = new bytes(fractionLength);
for (uint256 i = 0; i < fractionLength; i++) {
fractionTrimmed[i] = fractionBytes[i];
}
return string.concat(tokenWholeStr, ".", string(fractionTrimmed), " ", symbol);
}
}
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 safety checks and error handling for token formatting.

Several improvements needed:

  1. Add null address check
  2. Handle potential failures in external calls
  3. Similar gas optimizations as formatEth
     function formatToken(uint256 _amountWei, address _token) public view returns (string memory) {
+        require(_token != address(0), "EscrowView: Token address cannot be zero");
+        require(_token.code.length > 0, "EscrowView: Token must be a contract");
+
         IERC20Metadata token = IERC20Metadata(_token);
-        uint8 decimals = token.decimals();
-        string memory symbol = token.symbol();
+        
+        // Handle potential revert from non-compliant tokens
+        (bool success1, bytes memory data1) = _token.staticcall(
+            abi.encodeWithSelector(IERC20Metadata.decimals.selector)
+        );
+        require(success1 && data1.length >= 32, "EscrowView: Failed to get decimals");
+        uint8 decimals = abi.decode(data1, (uint8));
+
+        (bool success2, bytes memory data2) = _token.staticcall(
+            abi.encodeWithSelector(IERC20Metadata.symbol.selector)
+        );
+        require(success2 && data2.length > 0, "EscrowView: Failed to get symbol");
+        string memory symbol = abi.decode(data2, (string));
📝 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 formatToken(uint256 _amountWei, address _token) public view returns (string memory) {
IERC20Metadata token = IERC20Metadata(_token);
uint8 decimals = token.decimals();
string memory symbol = token.symbol();
uint256 tenToDecimals = uint256(10) ** uint256(decimals);
uint256 tokenWhole = _amountWei / tenToDecimals;
uint256 tokenFraction;
uint8 fractionDigits;
if (decimals >= 3) {
uint256 divider = uint256(10) ** uint256(decimals - 3);
tokenFraction = (_amountWei % tenToDecimals) / divider;
fractionDigits = 3;
} else {
tokenFraction = _amountWei % tenToDecimals;
fractionDigits = decimals;
}
string memory tokenWholeStr = Strings.toString(tokenWhole);
if (tokenFraction == 0) {
return string.concat(tokenWholeStr, " ", symbol);
}
string memory tokenFractionStr = Strings.toString(tokenFraction);
while (bytes(tokenFractionStr).length < fractionDigits) {
tokenFractionStr = string.concat("0", tokenFractionStr);
}
bytes memory fractionBytes = bytes(tokenFractionStr);
uint256 fractionLength = fractionBytes.length;
while (fractionLength > 0 && fractionBytes[fractionLength - 1] == bytes1("0")) {
fractionLength--;
}
if (fractionLength == 0) {
return string.concat(tokenWholeStr, " ", symbol);
} else {
bytes memory fractionTrimmed = new bytes(fractionLength);
for (uint256 i = 0; i < fractionLength; i++) {
fractionTrimmed[i] = fractionBytes[i];
}
return string.concat(tokenWholeStr, ".", string(fractionTrimmed), " ", symbol);
}
}
function formatToken(uint256 _amountWei, address _token) public view returns (string memory) {
require(_token != address(0), "EscrowView: Token address cannot be zero");
require(_token.code.length > 0, "EscrowView: Token must be a contract");
IERC20Metadata token = IERC20Metadata(_token);
// Handle potential revert from non-compliant tokens
(bool success1, bytes memory data1) = _token.staticcall(
abi.encodeWithSelector(IERC20Metadata.decimals.selector)
);
require(success1 && data1.length >= 32, "EscrowView: Failed to get decimals");
uint8 decimals = abi.decode(data1, (uint8));
(bool success2, bytes memory data2) = _token.staticcall(
abi.encodeWithSelector(IERC20Metadata.symbol.selector)
);
require(success2 && data2.length > 0, "EscrowView: Failed to get symbol");
string memory symbol = abi.decode(data2, (string));
uint256 tenToDecimals = uint256(10) ** uint256(decimals);
uint256 tokenWhole = _amountWei / tenToDecimals;
uint256 tokenFraction;
uint8 fractionDigits;
if (decimals >= 3) {
uint256 divider = uint256(10) ** uint256(decimals - 3);
tokenFraction = (_amountWei % tenToDecimals) / divider;
fractionDigits = 3;
} else {
tokenFraction = _amountWei % tenToDecimals;
fractionDigits = decimals;
}
string memory tokenWholeStr = Strings.toString(tokenWhole);
if (tokenFraction == 0) {
return string.concat(tokenWholeStr, " ", symbol);
}
string memory tokenFractionStr = Strings.toString(tokenFraction);
while (bytes(tokenFractionStr).length < fractionDigits) {
tokenFractionStr = string.concat("0", tokenFractionStr);
}
bytes memory fractionBytes = bytes(tokenFractionStr);
uint256 fractionLength = fractionBytes.length;
while (fractionLength > 0 && fractionBytes[fractionLength - 1] == bytes1("0")) {
fractionLength--;
}
if (fractionLength == 0) {
return string.concat(tokenWholeStr, " ", symbol);
} else {
bytes memory fractionTrimmed = new bytes(fractionLength);
for (uint256 i = 0; i < fractionLength; i++) {
fractionTrimmed[i] = fractionBytes[i];
}
return string.concat(tokenWholeStr, ".", string(fractionTrimmed), " ", symbol);
}
}

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

🧹 Nitpick comments (5)
contracts/deploy/00-escrow.ts (1)

51-56: Optimize the gas limit for EscrowView deployment.

The gas limit of 50000000 seems unnecessarily high for a view contract deployment, which typically requires much less gas.

 await deploy("EscrowView", {
   from: deployer,
   args: [escrow.target],
-  gasLimit: 50000000,
+  gasLimit: 1000000,
   log: true,
 });
contracts/scripts/getPayoutMessages.ts (3)

1-4: Remove commented import.

The commented import statement should be either used or removed to maintain clean code.

-// import { BigNumber, utils } from "ethers";
 import { task } from "hardhat/config";
 import { EscrowUniversal, EscrowView } from "../typechain-types";

13-22: Parameterize hardcoded values.

Consider extracting hardcoded values like the IPFS hash and ETH amount into configurable parameters.

+const DEFAULT_IPFS_HASH = "/ipfs/QmWRTngdTzTTq89L8r7XCwbRHrmuaNqzmvW7Cqk3s1FS9C";
+const DEFAULT_TRANSACTION_VALUE = "0.5";

 try {
   await escrow.createNativeTransaction(
     deadline,
-    "/ipfs/QmWRTngdTzTTq89L8r7XCwbRHrmuaNqzmvW7Cqk3s1FS9C",
+    DEFAULT_IPFS_HASH,
     seller.address,
     {
-      value: ethers.parseEther("0.5"),
+      value: ethers.parseEther(DEFAULT_TRANSACTION_VALUE),
     }
   );

63-65: Enhance error handling.

The current error handling simply logs the error. Consider adding error classification and appropriate handling strategies.

   } catch (error) {
-    console.log(error);
+    if (error instanceof Error) {
+      console.error(`Error executing payout messages test: ${error.message}`);
+      if (error.stack) console.debug(error.stack);
+    } else {
+      console.error("An unknown error occurred:", error);
+    }
+    process.exit(1);
   }
contracts/deployments/arbitrumSepoliaDevnet/DAI.json (1)

2-2: Verify test DAI deployment configuration.

The test DAI contract is deployed with:

  • Initial supply: 1,000,000 tokens
  • Name and symbol both set to "DAI"
    Consider using a different symbol (e.g., "tDAI") to clearly indicate this is a test token.

Also applies to: 291-325

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 53e8a88 and 7847882.

⛔ Files ignored due to path filters (3)
  • .yarn/releases/yarn-4.2.2.cjs is excluded by !**/.yarn/**
  • .yarn/releases/yarn-4.6.0.cjs is excluded by !**/.yarn/**
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (13)
  • .yarnrc.yml (1 hunks)
  • contracts/deploy/00-escrow.ts (3 hunks)
  • contracts/deployments/arbitrum/.chainId (1 hunks)
  • contracts/deployments/arbitrum/DAI.json (1 hunks)
  • contracts/deployments/arbitrum/WETH.json (1 hunks)
  • contracts/deployments/arbitrumSepoliaDevnet/DAI.json (1 hunks)
  • contracts/deployments/arbitrumSepoliaDevnet/WETH.json (1 hunks)
  • contracts/hardhat.config.ts (6 hunks)
  • contracts/package.json (2 hunks)
  • contracts/scripts/getPayoutMessages.ts (1 hunks)
  • contracts/src/EscrowView.sol (1 hunks)
  • package.json (1 hunks)
  • prettier-config/package.json (1 hunks)
✅ Files skipped from review due to trivial changes (4)
  • contracts/deployments/arbitrum/.chainId
  • prettier-config/package.json
  • .yarnrc.yml
  • contracts/deployments/arbitrumSepoliaDevnet/WETH.json
🔇 Additional comments (11)
contracts/deploy/00-escrow.ts (2)

4-4: LGTM! Enhanced type safety with explicit import.

The addition of the EscrowUniversal type import improves type checking and IDE support.


38-38: LGTM! Improved contract retrieval with type assertion.

The change to use ethers.getContract with explicit type assertion enhances type safety.

contracts/hardhat.config.ts (2)

20-21: LGTM! Improved account management.

The consolidated account configuration enhances maintainability and flexibility.


27-27: Verify impact of viaIR setting.

The viaIR setting enables the new IR-based codegen pipeline, which might affect compilation results.

✅ Verification successful

viaIR setting is properly configured and safe to use

The viaIR setting is correctly configured alongside the optimizer in a stable Solidity version (0.8.18). This combination is recommended for production use as it can enable better optimization opportunities. The successful contract deployments confirm the compilation pipeline is working as expected.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if viaIR affects contract size or gas usage

# Compare contract sizes
echo "Checking contract sizes..."
npx hardhat size-contracts

# Check gas usage
echo "Checking gas usage..."
REPORT_GAS=true npx hardhat test

Length of output: 370


Script:

#!/bin/bash
# Search for viaIR mentions and related optimization settings
echo "Searching for viaIR mentions..."
rg -i "viaIR" -A 3 -B 3

echo -e "\nChecking for optimization settings..."
rg -i "optimization|optimizer" -A 3 -B 3

echo -e "\nChecking documentation files..."
fd -e md -e txt -x cat {}

Length of output: 77443

package.json (1)

26-29: LGTM! Consistent package manager configuration.

The updates to packageManager and volta configuration maintain consistency in dependency management.

contracts/deployments/arbitrum/WETH.json (1)

2-2: Verify the WETH contract address on Arbitrum.

The contract address matches the official Arbitrum WETH contract. However, let's verify its implementation and admin controls.

✅ Verification successful

WETH contract implementation and admin controls verified

The contract at 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1 is the official Arbitrum WETH implementation:

  • Uses standard WETH9 implementation with expected functionality
  • Protected by transparent proxy pattern with standard admin controls
  • Matches the canonical L2 bridge deployment
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify WETH contract implementation and admin on Arbitrum

# Check contract code and admin
curl -s -X POST "https://arb1.arbitrum.io/rpc" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc":"2.0",
    "method":"eth_getCode",
    "params":["0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", "latest"],
    "id":1
  }'

Length of output: 4447


Script:

#!/bin/bash
# Get implementation and admin addresses from storage slots

# Implementation slot: 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc
curl -s -X POST "https://arb1.arbitrum.io/rpc" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc":"2.0",
    "method":"eth_getStorageAt",
    "params":["0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", "latest"],
    "id":1
  }'

# Admin slot: 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103
curl -s -X POST "https://arb1.arbitrum.io/rpc" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc":"2.0",
    "method":"eth_getStorageAt",
    "params":["0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", "0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103", "latest"],
    "id":1
  }'

Length of output: 805


Script:

#!/bin/bash
# Get implementation contract bytecode
curl -s -X POST "https://arb1.arbitrum.io/rpc" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc":"2.0",
    "method":"eth_getCode",
    "params":["0x8b194beae1d3e0788a1a35173978001acdfba668", "latest"],
    "id":1
  }'

Length of output: 17439

contracts/package.json (1)

14-14: Consider using a newer Node.js version.

The Node.js version has been downgraded from 20.11.0 to 18.20.6. While Node.js 18 is still in LTS, consider using Node.js 20 for:

  • Better performance
  • Latest security updates
  • Newer features
contracts/src/EscrowView.sol (3)

85-121: Bug: Missing " ETH" for zero-fraction scenarios.

When the fractional part is not zero initially but ends up fully trimmed, line 113 returns ethWholeStr without appending " ETH", conflicting with the behavior in line 94.


127-175: Add safety checks and error handling for token formatting.

The function needs additional safety checks:

  1. Null address check
  2. Handle potential failures in external calls
  3. Similar gas optimizations as formatEth

26-80: ⚠️ Potential issue

Add input validation and error handling for getPayoutMessages.

The function should validate the transaction ID and handle non-existent transactions. Also, consider adding a check for the dispute status.

Apply this diff to add validation:

 function getPayoutMessages(
     uint256 _transactionID
 ) external view returns (string memory noWinner, string memory buyerWins, string memory sellerWins) {
+    (, , , , , , , Status status, , , , IERC20 token) = escrow.transactions(_transactionID);
+    require(status != Status.NoDispute, "EscrowView: No dispute created yet");
+    require(status != Status.Resolved, "EscrowView: Dispute already resolved");

     (, , uint256 amount, , , , , uint256 buyerFee, uint256 sellerFee, , , IERC20 token) = escrow.transactions(
         _transactionID
     );

Likely invalid or redundant comment.

contracts/deployments/arbitrum/DAI.json (1)

2-2: Verify the DAI contract address on Arbitrum.

The contract address matches the official Arbitrum DAI contract. Let's verify its implementation.

✅ Verification successful

✓ DAI contract address and implementation verified on Arbitrum

The address matches the official DAI token contract on Arbitrum, and bytecode analysis confirms this is the legitimate MakerDAO DAI implementation with all standard functionality and security features intact.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify DAI contract implementation on Arbitrum

# Check contract code and verify it's the official DAI implementation
curl -s -X POST "https://arb1.arbitrum.io/rpc" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc":"2.0",
    "method":"eth_getCode",
    "params":["0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1", "latest"],
    "id":1
  }'

Length of output: 10115

console.log("Seller proposes settlement");
await escrow.connect(seller).proposeSettlement(txId, ethers.parseEther("0.4"));

await new Promise((resolve) => setTimeout(resolve, 3000));
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

Replace setTimeout with network time manipulation.

Using setTimeout for testing is unreliable. Consider using hardhat's time manipulation utilities instead.

-    await new Promise((resolve) => setTimeout(resolve, 3000));
+    await ethers.provider.send("evm_increaseTime", [3]);
+    await ethers.provider.send("evm_mine", []);
📝 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
await new Promise((resolve) => setTimeout(resolve, 3000));
await ethers.provider.send("evm_increaseTime", [3]);
await ethers.provider.send("evm_mine", []);

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: 0

🧹 Nitpick comments (1)
contracts/scripts/generateDeploymentsMarkdown.sh (1)

9-10: LGTM! Consider grouping related artifacts.

The addition of DAI and WETH to ignored artifacts is appropriate as they are external dependencies.

Consider grouping related artifacts together for better maintainability:

 IGNORED_ARTIFACTS=(
     "CREATE3Factory.json"
     "MetaEvidence_*"
     "PNK.json"
+    # External token contracts
     "DAI.json"
     "WETH.json"
     "RandomizerOracle.json"
     "_Implementation.json"
     "_Proxy.json"
 )
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7847882 and fb510ed.

📒 Files selected for processing (2)
  • contracts/README.md (1 hunks)
  • contracts/scripts/generateDeploymentsMarkdown.sh (1 hunks)
🔇 Additional comments (1)
contracts/README.md (1)

19-20: LGTM! Verify contract deployments on Arbiscan.

The contract addresses and links are correctly formatted. Let's verify the contract deployments:

Base automatically changed from feat/template-and-mappings-arbitrum-one to master February 5, 2025 13:14
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.

1 participant