Skip to content

Fix/english auction bid invariants#61

Open
Rav1Chauhan wants to merge 3 commits intoStabilityNexus:mainfrom
Rav1Chauhan:fix/english-auction-bid-invariants
Open

Fix/english auction bid invariants#61
Rav1Chauhan wants to merge 3 commits intoStabilityNexus:mainfrom
Rav1Chauhan:fix/english-auction-bid-invariants

Conversation

@Rav1Chauhan
Copy link

@Rav1Chauhan Rav1Chauhan commented Feb 24, 2026

Addressed Issues:

Fixes #50

This PR strengthens the bidding logic of EnglishAuction by enforcing strict
minimum bid increment rules and adds comprehensive invariant tests.

The change prevents equal-value bid replacement, griefing, and unnecessary
refund churn.

Problem

Ascending auctions allowed potential bid replacement without meaningful
economic increase if misconfigured.

This could enable:

  • Spam bidding
  • Griefing behavior
  • Gas refund churn
  • DOS-like auction interference

Solution

Contract Changes

  • Enforced:
    • First bid must be >= minimumBid
    • Subsequent bids must be >= highestBid + minBidDelta
  • Added validation:

require(minBidDelta > 0, "minBidDelta must be > 0");

  • Prevented equal bids
  • Ensured strict increment enforcement

Tests Added

New file:

test/EnglishAuction.bidding.test.js

Covers the following invariants:

  1. First bid must be ≥ minimumBid
  2. Subsequent bid must be ≥ highestBid + minBidDelta
  3. Equal bids revert
  4. minBidDelta == 0 reverts

All tests pass successfully.

Impact

  • Prevents spam and grief bidding
  • Strengthens economic safety
  • Improves auction correctness
  • Aligns with coding guideline:
    "Verify that any modification to contract logic includes corresponding updates to automated tests."

Testing

npx hardhat test

All tests passing.

No new warnings or errors.

Screenshots/Recordings

N/A — Smart contract logic update only.

Additional Notes

This PR affects only EnglishAuction logic and related tests.
No changes were made to other auction types.

Checklist

  • [ x] My PR addresses a single issue, fixes a single bug or makes a single improvement.
  • [x ] My code follows the project's code style and conventions.
  • If applicable, I have made corresponding changes or additions to the documentation.
  • [ x] If applicable, I have made corresponding changes or additions to tests.
  • [ x] My changes generate no new warnings or errors.
  • [ x] I have joined the Stability Nexus's Discord server and I will share a link to this PR with the project maintainers there.
  • [x ] I have read the Contribution Guidelines.
  • [x ] Once I submit my PR, CodeRabbit AI will automatically review it and I will address CodeRabbit's comments.

AI Usage Disclosure

Check one of the checkboxes below:

  • This PR does not contain AI-generated code at all.
  • [x ] This PR contains AI-generated code. I have tested the code locally and I am responsible for it.

I have used the following AI models and tools: TODO

⚠️ AI Notice - Important!

We encourage contributors to use AI tools responsibly when creating Pull Requests. While AI can be a valuable aid, it is essential to ensure that your contributions meet the task requirements, build successfully, include relevant tests, and pass all linters. Submissions that do not meet these standards may be closed without warning to maintain the quality and integrity of the project. Please take the time to understand the changes you are proposing and their impact.

Summary by CodeRabbit

  • Bug Fixes

    • Strengthened auction bidding validation: minimum bid must be > 0, min bid increment must be > 0, first bids must meet minimum, subsequent bids must exceed current highest by the required delta.
    • Fixed bid funds handling to ensure correct processing during refunds.
  • Tests

    • Added comprehensive bidding test suite covering minimums, increments, and reverts.
    • Added a mock ERC20 token to support tests.

@coderabbitai
Copy link

coderabbitai bot commented Feb 24, 2026

Walkthrough

Enforces positive minimumBid and minBidDelta, requires first bids ≥ minimumBid and subsequent bids ≥ highestBid + minBidDelta, adjusts funds/refund assignment in bid flow, adds an ERC20Mock and tests validating bidding invariants.

Changes

Cohort / File(s) Summary
Auction contract
contracts/EnglishAuction.sol
Added minimumBid > 0 and minBidDelta > 0 validation in createAuction; tightened bid logic (first vs subsequent bids) requiring increments; adjusted availableFunds assignment and refund ordering; minor formatting.
Test & Mocks
contracts/mocks/ERC20Mock.sol, test/EnglishAuction.bidding.test.js
New ERC20Mock for token testing; added bidding test suite covering first-bid minimum, minBidDelta enforcement, prevention of equal bids, and invalid-zero parameter cases.

Sequence Diagram(s)

sequenceDiagram
    participant Bidder
    participant ERC20
    participant EnglishAuction
    participant PrevBidder

    Bidder->>ERC20: approve(spender=EnglishAuction, amount)
    Bidder->>EnglishAuction: createAuction(...) / (seller deploys) 
    Bidder->>EnglishAuction: bid(amount)
    EnglishAuction->>ERC20: transferFrom(Bidder, contract, amount)
    alt previous highest exists
        EnglishAuction->>PrevBidder: refund(previousAmount)
        EnglishAuction->>EnglishAuction: set availableFunds = amount
        EnglishAuction->>EnglishAuction: update highestBid, highestBidder
    else first bid
        EnglishAuction->>EnglishAuction: require amount >= minimumBid
        EnglishAuction->>EnglishAuction: set availableFunds = amount
        EnglishAuction->>EnglishAuction: set highestBid, highestBidder
    end
    EnglishAuction-->>Bidder: emit BidPlaced / revert if validations fail
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Suggested labels

Solidity Lang, Typescript Lang

Suggested reviewers

  • ceilican

Poem

🐰 I hopped in to guard each rising bid,
No equal stakes to make the auction skid.
Tokens mocked, tests tight in sight,
Every new bid must climb the height. ✨

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fix/english auction bid invariants' directly reflects the main change: enforcing bidding invariants (minimum bid and minimum bid increment) in the EnglishAuction contract.
Linked Issues check ✅ Passed The PR fully addresses Issue #50 by enforcing a configurable minimum bid increment (minBidDelta) and preventing equal-value bids, with comprehensive test coverage validating the bidding invariants.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing English auction bid invariants: contract logic improvements, new ERC20Mock for testing, and new bidding test suite with no unrelated modifications.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@contracts/EnglishAuction.sol`:
- Around line 67-68: There are two identical validation lines checking duration:
the duplicate require(duration > 0, 'Duration must be greater than zero
seconds'); removes unnecessary gas; delete the redundant require and keep a
single instance of require(duration > 0, 'Duration must be greater than zero
seconds') in the function/constructor that validates the auction duration (where
that require appears) so only one check remains.
- Around line 113-119: Replace the fragile "first bid" sentinel that checks
auction.highestBid == 0 with an explicit auctioneer sentinel (check
auction.auctioneer == address(0)) so a zero-value first bid doesn't keep
treating the auction as bidless; specifically, in the bidding logic (where
auction.highestBid, auction.minimumBid and auction.minBidDelta are used) change
the branch to require(bidAmount >= auction.minimumBid) when auction.auctioneer
== address(0), otherwise require(bidAmount >= auction.highestBid +
auction.minBidDelta) for subsequent bids and then update auction.auctioneer
appropriately.

In `@test/EnglishAuction.bidding.test.js`:
- Around line 38-82: Update the test helper createAuction to accept a
configurable minimumBid (currently hardcoded to ethers.parseEther('10')) in
addition to minBidDelta, then add a new test that creates an auction with
minimumBid == 0 and verifies first bids are subject to minBidDelta enforcement:
call createAuction( /* minBidDelta */, /* minimumBid = 0 */ ) and assert that a
first bid lower than minBidDelta reverts (or is rejected) while a bid >=
minBidDelta succeeds; reference the helper createAuction and the
auction.bid(...) calls to locate where to change tests and assertions.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eea3b08 and 8b99109.

📒 Files selected for processing (3)
  • contracts/EnglishAuction.sol
  • contracts/mocks/ERC20Mock.sol
  • test/EnglishAuction.bidding.test.js

Comment on lines +113 to +119
// First bid must meet minimumBid
if (auction.highestBid == 0) {
require(bidAmount >= auction.minimumBid, 'Bid below minimum');
} else {
// Enforce strict increment
require(bidAmount >= auction.highestBid + auction.minBidDelta, 'Bid increment too low');
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

First-bid detection can be bypassed when minimumBid is 0.
With highestBid == 0 as the sole sentinel, a zero first bid leaves highestBid at 0, so subsequent bids are still treated as “first bids” and can ignore minBidDelta. That reintroduces equal-bid replacement and spam risk when minimumBid is zero.

Proposed fix (use auctioneer sentinel)
-        if (auction.highestBid == 0) {
+        if (auction.highestBid == 0 && auction.winner == auction.auctioneer) {
             require(bidAmount >= auction.minimumBid, 'Bid below minimum');
         } else {
             // Enforce strict increment
             require(bidAmount >= auction.highestBid + auction.minBidDelta, 'Bid increment too low');
         }

As per coding guidelines: "Review for common smart contract vulnerabilities, including but not limited to improper input validation."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/EnglishAuction.sol` around lines 113 - 119, Replace the fragile
"first bid" sentinel that checks auction.highestBid == 0 with an explicit
auctioneer sentinel (check auction.auctioneer == address(0)) so a zero-value
first bid doesn't keep treating the auction as bidless; specifically, in the
bidding logic (where auction.highestBid, auction.minimumBid and
auction.minBidDelta are used) change the branch to require(bidAmount >=
auction.minimumBid) when auction.auctioneer == address(0), otherwise
require(bidAmount >= auction.highestBid + auction.minBidDelta) for subsequent
bids and then update auction.auctioneer appropriately.

Copy link

@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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@contracts/EnglishAuction.sol`:
- Around line 113-121: Remove the redundant runtime check
require(auction.minBidDelta > 0, 'Invalid minBidDelta') from the bid handling
logic because minBidDelta is already validated during createAuction and never
mutated afterwards; simply delete that require in the function that processes
bids (the block referencing auction.highestBid, auction.minimumBid,
auction.minBidDelta) to save gas while leaving the earlier validation in
createAuction intact.

In `@test/EnglishAuction.bidding.test.js`:
- Around line 72-78: The test currently asserts a generic revert for
auction.connect(bidder2).bid(0, ethers.parseEther('10')); update this to assert
the explicit revert reason from the contract (e.g., replace .to.be.reverted with
.to.be.revertedWith('<REVERT_REASON>')) so the failure proves the bid-increment
rule triggered; locate the assertion in the '3️⃣ Equal bid should revert' test
that follows createAuction(...) and the prior auction.connect(bidder1).bid(...)
call and substitute the correct revert string from the auction contract.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8b99109 and 1749658.

📒 Files selected for processing (2)
  • contracts/EnglishAuction.sol
  • test/EnglishAuction.bidding.test.js

Comment on lines +113 to +121
// First bid must meet minimumBid
if (auction.highestBid == 0) {
require(bidAmount >= auction.minimumBid, 'Bid below minimum');
} else {
// Enforce strict increment
require(bidAmount >= auction.highestBid + auction.minBidDelta, 'Bid increment too low');
}

require(auction.minBidDelta > 0, 'Invalid minBidDelta');
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Remove redundant minBidDelta runtime check to save gas.
minBidDelta is already validated in createAuction and is not mutated afterward, so this per-bid require adds cost without improving safety.

♻️ Suggested change
-        require(auction.minBidDelta > 0, 'Invalid minBidDelta');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/EnglishAuction.sol` around lines 113 - 121, Remove the redundant
runtime check require(auction.minBidDelta > 0, 'Invalid minBidDelta') from the
bid handling logic because minBidDelta is already validated during createAuction
and never mutated afterwards; simply delete that require in the function that
processes bids (the block referencing auction.highestBid, auction.minimumBid,
auction.minBidDelta) to save gas while leaving the earlier validation in
createAuction intact.

Comment on lines +72 to +78
it('3️⃣ Equal bid should revert', async function () {
await createAuction(ethers.parseEther('1'));

await auction.connect(bidder1).bid(0, ethers.parseEther('10'));

await expect(auction.connect(bidder2).bid(0, ethers.parseEther('10'))).to.be.reverted;
});
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Make the revert reason explicit for clarity.
This ensures the failure is due to the bid-increment rule, not another precondition.

✅ Suggested change
-        await expect(auction.connect(bidder2).bid(0, ethers.parseEther('10'))).to.be.reverted;
+        await expect(auction.connect(bidder2).bid(0, ethers.parseEther('10'))).to.be.revertedWith('Bid increment too low');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/EnglishAuction.bidding.test.js` around lines 72 - 78, The test currently
asserts a generic revert for auction.connect(bidder2).bid(0,
ethers.parseEther('10')); update this to assert the explicit revert reason from
the contract (e.g., replace .to.be.reverted with
.to.be.revertedWith('<REVERT_REASON>')) so the failure proves the bid-increment
rule triggered; locate the assertion in the '3️⃣ Equal bid should revert' test
that follows createAuction(...) and the prior auction.connect(bidder1).bid(...)
call and substitute the correct revert string from the auction contract.

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.

Auctions accept bids equal to the current highest bid — griefing possible

1 participant