-
Notifications
You must be signed in to change notification settings - Fork 67
feat/escrow deposit #233
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
base: main
Are you sure you want to change the base?
feat/escrow deposit #233
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -165,4 +165,19 @@ impl Events { | |
| } | ||
| .publish(env); | ||
| } | ||
|
|
||
| /// Emits a DEPOSIT event with topics (symbol!("DEPOSIT"), commitment) | ||
| /// and data (owner, amount, new_balance). | ||
| pub fn deposit( | ||
| env: &Env, | ||
| commitment: BytesN<32>, | ||
| owner: Address, | ||
| amount: i128, | ||
| new_balance: i128, | ||
| ) { | ||
| env.events().publish( | ||
| (symbol_short!("DEPOSIT"), commitment), | ||
| (owner, amount, new_balance), | ||
| ); | ||
| } | ||
|
Comment on lines
+168
to
+182
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix Clippy error: use The pipeline is failing because 🐛 Proposed fix using the contractevent patternAdd the event struct near the other event definitions (around line 79): /// Event emitted when tokens are deposited into a vault.
#[contractevent]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct DepositEvent {
/// The commitment identifier of the vault receiving the deposit.
#[topic]
pub commitment: BytesN<32>,
/// The address of the vault owner who made the deposit.
pub owner: Address,
/// The amount of tokens deposited.
pub amount: i128,
/// The new balance after the deposit.
pub new_balance: i128,
}Then update the helper: - /// Emits a DEPOSIT event with topics (symbol!("DEPOSIT"), commitment)
- /// and data (owner, amount, new_balance).
+ /// Emits a `DepositEvent` to the host.
pub fn deposit(
env: &Env,
commitment: BytesN<32>,
owner: Address,
amount: i128,
new_balance: i128,
) {
- env.events().publish(
- (symbol_short!("DEPOSIT"), commitment),
- (owner, amount, new_balance),
- );
+ DepositEvent {
+ commitment,
+ owner,
+ amount,
+ new_balance,
+ }
+ .publish(env);
}🧰 Tools🪛 GitHub Actions: Checks[error] 178-178: Clippy (with -D warnings) failed: use of deprecated method [error] Command 🤖 Prompt for AI Agents |
||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -103,6 +103,39 @@ impl EscrowContract { | |||||||||
| Events::vault_crt(&env, commitment, token, owner); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| /// Deposits tokens into an active vault. | ||||||||||
| /// | ||||||||||
| /// The vault owner must authorize the call. Funds are transferred from the | ||||||||||
| /// owner to this contract and reflected in the vault's tracked balance. | ||||||||||
| /// | ||||||||||
| /// ### Errors | ||||||||||
| /// - `InvalidAmount`: If `amount <= 0`. | ||||||||||
| /// - `VaultNotFound`: If the vault does not exist. | ||||||||||
| /// - `VaultInactive`: If the vault has been cancelled/inactivated. | ||||||||||
| pub fn deposit(env: Env, commitment: BytesN<32>, amount: i128) { | ||||||||||
| if amount <= 0 { | ||||||||||
| panic_with_error!(&env, EscrowError::InvalidAmount); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| let config = read_vault_config(&env, &commitment) | ||||||||||
| .unwrap_or_else(|| panic_with_error!(&env, EscrowError::VaultNotFound)); | ||||||||||
| config.owner.require_auth(); | ||||||||||
|
|
||||||||||
| let mut state = read_vault_state(&env, &commitment) | ||||||||||
| .unwrap_or_else(|| panic_with_error!(&env, EscrowError::VaultNotFound)); | ||||||||||
| if !state.is_active { | ||||||||||
| panic_with_error!(&env, EscrowError::VaultInactive); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| let token_client = token::Client::new(&env, &config.token); | ||||||||||
| token_client.transfer(&config.owner, &env.current_contract_address(), &amount); | ||||||||||
|
Comment on lines
+130
to
+131
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix Clippy error: remove needless borrow on The pipeline is failing due to 🐛 Proposed fix let token_client = token::Client::new(&env, &config.token);
- token_client.transfer(&config.owner, &env.current_contract_address(), &amount);
+ token_client.transfer(&config.owner, env.current_contract_address(), &amount);📝 Committable suggestion
Suggested change
🧰 Tools🪛 GitHub Actions: Checks[error] 131-131: Clippy (with -D warnings) failed: needless borrows for generic args (rule: clippy::needless-borrows-for-generic-args). Borrowed expression does not implement required traits; help suggests using [error] Command 🤖 Prompt for AI Agents |
||||||||||
|
|
||||||||||
| state.balance += amount; | ||||||||||
| write_vault_state(&env, &commitment, &state); | ||||||||||
|
|
||||||||||
| Events::deposit(&env, commitment, config.owner, amount, state.balance); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| /// Schedules a payment from one vault to another. | ||||||||||
| /// | ||||||||||
| /// Funds are reserved in the source vault immediately upon scheduling. | ||||||||||
|
|
||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: Alien-Protocol/Alien-Gateway
Length of output: 746
Critical: PR does not implement stated objectives.
This PR claims to implement
deposit(env: Env, commitment: BytesN<32>, amount: i128)for EscrowContract (issue#189), including vault owner authentication, token transfers, VaultState.balance updates, DEPOSIT event emission, and validation. However, the deposit function does not exist in escrow_contract/src/lib.rs, and no DEPOSIT or DepositEvent definitions exist in escrow_contract/src/events.rs.Additionally, the file under review (
auction_contract/auction.md) is a specification document for AuctionContract with functions likecreate_auction,place_bid, andclaim_username—none of which relate to the escrow deposit feature.The escrow_contract directory structure is in place, but the actual implementation of the deposit feature is missing. Commit the implementation to:
🧰 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
assettoAuctionKey::Asset(id). - Writesmin_bidtoAuctionKey::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_bid
toAuctionKey::MinBid(id). - Writesend_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::NotWinnerwhen claimant is not highest bidder. #### S...(ENGLISH_WORD_REPEAT_BEGINNING_RULE)
🤖 Prompt for AI Agents