Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .github/workflows/sdk_tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: SDK Tests

on:
push:
branches: ["main"]
pull_request:
branches: ["main"]

jobs:
sdk-tests:
runs-on: ubuntu-latest
defaults:
run:
working-directory: zk/sdk

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20

- name: Install dependencies
run: npm install

- name: Run SDK tests
run: npm test
329 changes: 329 additions & 0 deletions gateway-contract/contracts/auction_contract/auction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,329 @@
# Auction Contract Specification

The Auction contract implements two auction flows that coexist in the same contract:

- A singleton username auction flow keyed by instance storage (`close_auction`, `claim_username`).
- An ID-indexed auction flow keyed by persistent storage (`create_auction`, `place_bid`, `close_auction_by_id`, `claim`).

Both flows use Soroban auth (`require_auth`) and ledger timestamp checks to enforce access and timing constraints.

## Public Entry Points

### Function: `create_auction`

Creates a new auction identified by `id`.

#### Interface

```rust
pub fn create_auction(
env: Env,
id: u32,
seller: Address,
asset: Address,
min_bid: i128,
end_time: u64,
)
```

#### Authorization

- `seller.require_auth()` must succeed.

#### Requirements & Validation

- Auction ID must not already exist (`storage::auction_exists(&env, id) == false`).
- If ID already exists, function aborts with `AuctionError::AuctionNotOpen`.

#### State Transitions

- Writes `seller` to `AuctionKey::Seller(id)`.
- Writes bidding token `asset` to `AuctionKey::Asset(id)`.
- Writes `min_bid` to `AuctionKey::MinBid(id)`.
- Writes `end_time` to `AuctionKey::EndTime(id)`.
- Sets status to `AuctionStatus::Open` in `AuctionKey::Status(id)`.

#### Events Emitted

- None in current implementation.

#### Errors

- `AuctionError::AuctionNotOpen` when `id` already exists.
- Host auth failure if `seller` does not authorize.

#### Edge Cases

- **Duplicate auction ID**: explicitly rejected (panic with `AuctionNotOpen`).
- **No min/end validation**: contract currently does not enforce `min_bid > 0` or `end_time > now` at creation.

### Function: `place_bid`

Places a bid on an existing auction and refunds the previously highest bidder.

#### Interface

```rust
pub fn place_bid(env: Env, id: u32, bidder: Address, amount: i128)
```

#### Authorization

- `bidder.require_auth()` must succeed.

#### Requirements & Validation

- Auction must still be open by time: `env.ledger().timestamp() < auction_end_time`.
- Bid must satisfy both:
- `amount >= min_bid`
- `amount > highest_bid`

If timing check fails, function aborts with `AuctionError::AuctionNotOpen`.
If bid floor/outbid check fails, function aborts with `AuctionError::BidTooLow`.

#### State Transitions

1. Transfers `amount` of auction asset token from `bidder` to contract.
2. If previous highest bidder exists, transfers prior `highest_bid` from contract back to that bidder.
3. Updates `AuctionKey::HighestBidder(id)` to current `bidder`.
4. Updates `AuctionKey::HighestBid(id)` to `amount`.

#### Events Emitted

- None in current implementation.

#### Errors

- `AuctionError::AuctionNotOpen` when auction time window is closed.
- `AuctionError::BidTooLow` when bid is below min or not strictly above current highest.
- Host auth failure if `bidder` does not authorize.
- Token transfer failure if token contract transfer preconditions are not met.

#### Edge Cases

- **Zero-bid history**: first bid is accepted if it meets `min_bid` and auction is still open.
- **Equal-to-highest bid**: rejected (`amount <= highest_bid` path).
- **Late bid at exact end timestamp**: rejected because condition is `timestamp >= end_time`.

### Function: `close_auction_by_id`

Closes an ID-indexed auction once its end time has passed.

#### Interface

```rust
pub fn close_auction_by_id(env: Env, id: u32)
```

#### Authorization

- No explicit caller auth in current implementation.

#### Requirements & Validation

- Current ledger timestamp must be at least the auction end time.
- If `timestamp < end_time`, function aborts with `AuctionError::AuctionNotClosed`.

#### State Transitions

- Sets `AuctionKey::Status(id)` to `AuctionStatus::Closed`.

#### Events Emitted

- None in current implementation.

#### Errors

- `AuctionError::AuctionNotClosed` when called before end time.

#### Edge Cases

- **Early close attempt**: rejected with `AuctionNotClosed`.
- **No bids placed**: still closes successfully; later claim semantics determine payout/ownership behavior.

### Function: `close_auction`

Closes the singleton username auction flow and emits closure metadata.

#### Interface

```rust
pub fn close_auction(
env: Env,
username_hash: BytesN<32>,
) -> Result<(), AuctionError>
```

#### Authorization

- No explicit caller auth in current implementation.

#### Requirements & Validation

- Current instance `status` must be `AuctionStatus::Open`.
- Current ledger timestamp must be at least instance `end_time`.

Returns:

- `Err(AuctionError::AuctionNotOpen)` if status is not `Open`.
- `Err(AuctionError::AuctionNotClosed)` if called before end time.

#### State Transitions

- Sets instance `DataKey::Status` to `AuctionStatus::Closed`.
- Reads instance `DataKey::HighestBidder` and `DataKey::HighestBid` for event payload.

#### Events Emitted

- Emits `AuctionClosedEvent` via `emit_auction_closed` with:
- `username_hash`
- `winner: Option<Address>`
- `winning_bid: u128`

#### Errors

- `AuctionError::AuctionNotOpen`
- `AuctionError::AuctionNotClosed`

#### Edge Cases

- **Zero bids**: event emits `winner = None`, `winning_bid = 0`.
- **Repeated close**: second close call fails with `AuctionNotOpen` because status is no longer `Open`.

### Function: `claim_username`

Allows winner of singleton username auction to deploy/claim the username via factory contract.

#### Interface

```rust
pub fn claim_username(
env: Env,
username_hash: BytesN<32>,
claimer: Address,
) -> Result<(), AuctionError>
```

#### Authorization

- `claimer.require_auth()` must succeed.

#### Requirements & Validation

- Instance status must not already be `Claimed`.
- Instance status must be `Closed`.
- `claimer` must equal stored highest bidder.
- Factory contract address must exist in `DataKey::FactoryContract`.

Returns:

- `Err(AuctionError::AlreadyClaimed)` if already claimed.
- `Err(AuctionError::NotClosed)` if not closed.
- `Err(AuctionError::NotWinner)` if caller is not winner.
- `Err(AuctionError::NoFactoryContract)` if factory address is missing.

#### State Transitions

1. Sets instance `DataKey::Status` to `AuctionStatus::Claimed`.
2. Invokes factory contract method `deploy_username(username_hash, claimer)`.

#### Events Emitted

- Emits `UsernameClaimedEvent` via `emit_username_claimed` with:
- `username_hash`
- `claimer`

#### Errors

- `AuctionError::AlreadyClaimed`
- `AuctionError::NotClosed`
- `AuctionError::NotWinner`
- `AuctionError::NoFactoryContract`
- Host auth failure if `claimer` does not authorize.

#### Edge Cases

- **No bids**: no highest bidder exists, so claim fails with `NotWinner`.
- **Claim race**: first valid claim sets status to `Claimed`; subsequent claims fail with `AlreadyClaimed`.

### Function: `claim`

Finalizes an ID-indexed auction by allowing the winner to release funds to seller.

#### Interface

```rust
pub fn claim(env: Env, id: u32, claimant: Address)
```

#### Authorization

- `claimant.require_auth()` must succeed.

#### Requirements & Validation

- Auction status for `id` must be `AuctionStatus::Closed`.
- Auction must not already be claimed (`auction_is_claimed == false`).
- `claimant` must equal current highest bidder.

Function aborts with:

- `AuctionError::NotClosed` when status is not closed.
- `AuctionError::AlreadyClaimed` when already claimed.
- `AuctionError::NotWinner` when claimant is not highest bidder.

#### State Transitions

1. Reads token `asset`, `winning_bid`, and `seller` for auction `id`.
2. Transfers `winning_bid` from contract to `seller`.
3. Marks `AuctionKey::Claimed(id)` as `true`.

#### Events Emitted

- None in current implementation.

#### Errors

- `AuctionError::NotClosed`
- `AuctionError::AlreadyClaimed`
- `AuctionError::NotWinner`
- Host auth failure if `claimant` does not authorize.
- Token transfer failure if transfer cannot be completed.

#### Edge Cases

- **No bids**: highest bidder is `None`; all claim attempts fail with `NotWinner`.
- **Double claim**: second successful claimant attempt is blocked by `AlreadyClaimed`.

## Error Variants (Contract-Wide)

Defined in `errors.rs`:

- `NotWinner`
- `AlreadyClaimed`
- `NotClosed`
- `NoFactoryContract`
- `Unauthorized`
- `InvalidState`
- `BidTooLow`
- `AuctionNotOpen`
- `AuctionNotClosed`

Note: `Unauthorized` and `InvalidState` are defined but not currently emitted by the public entry points above; authorization failures are enforced primarily through host-level `require_auth`.

## Event Types (Contract-Wide)

Defined in `events.rs`:

- `AuctionCreatedEvent`
- `BidPlacedEvent`
- `AuctionClosedEvent`
- `UsernameClaimedEvent`
- `BidRefundedEvent`

Current emission in public entry points:

- `close_auction` emits `AuctionClosedEvent`.
- `claim_username` emits `UsernameClaimedEvent`.
- Other listed entry points currently emit no events.

Comment on lines +1 to +329
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

PR scope appears misaligned with the stated objective (SMT stale-root replay test).

This change set documents the auction contract, but the objective for issue #211 requires adding test_register_resolver_stale_root_after_first_registration and documenting sequencing in gateway-contract/contracts/core_contract/core.md. As-is, the acceptance criteria look unmet in this PR payload.

🧰 Tools
🪛 LanguageTool

[style] ~42-~42: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...n asset to AuctionKey::Asset(id). - Writes min_bid to AuctionKey::MinBid(id). ...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~43-~43: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...min_bidtoAuctionKey::MinBid(id). - Writes end_timetoAuctionKey::EndTime(id)`...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~222-~222: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...losed. - Err(AuctionError::NotWinner) if caller is not winner. - `Err(AuctionErr...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~223-~223: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ... Err(AuctionError::NoFactoryContract) if factory address is missing. #### State...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~273-~273: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...dy claimed. - AuctionError::NotWinner when claimant is not highest bidder. #### S...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)

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

In `@gateway-contract/contracts/auction_contract/auction.md` around lines 1 - 329,
The PR added an auction contract spec unrelated to the required SMT stale-root
replay work; to fix, replace or split this payload so the PR contains the test
and docs requested by issue `#211`: add the test function named
test_register_resolver_stale_root_after_first_registration to the existing core
contract test suite (locate the test module where other resolver/stale-root
tests live and implement the sequencing assertions described by the issue), and
update gateway-contract/contracts/core_contract/core.md to document the
sequencing behavior and expected stale-root replay behavior (include exact
steps/assertions used by the new test). If the auction documentation must
remain, move it to a separate PR or branch so this PR only contains the new test
and the core.md sequencing documentation.

12 changes: 12 additions & 0 deletions gateway-contract/contracts/core_contract/core.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Core Contract Notes

## SMT Root Sequencing Requirement

`register_resolver` enforces strict root sequencing:

- `public_signals.old_root` must exactly equal the current on-chain SMT root.
- A successful `register_resolver` updates the on-chain root to `public_signals.new_root`.
- Any later call reusing the pre-update root is rejected as stale.

This replay protection prevents re-submitting proofs against an already-consumed root. In tests, the stale replay path is asserted to panic with `Error(Contract, #4)` (`StaleRoot`).

Loading
Loading