Skip to content

Latest commit

 

History

History
121 lines (86 loc) · 3.61 KB

File metadata and controls

121 lines (86 loc) · 3.61 KB

Rebalance Auction House

A Dutch-auction settlement module for vaults, index products, and treasury managers that need to exchange portfolio assets transparently on-chain.

Sellers escrow an exact amount of one ERC-20, define a descending normalized price curve, and let solvers fill any remaining portion atomically. An optional oracle floor prevents fills outside a configured market-price deviation.

Educational portfolio implementation. The contracts have not been independently audited and must not be used to secure production funds.

Flow

flowchart LR
    A["Seller escrows sell token"] --> B["Dutch auction starts"]
    B --> C["Solvers fill portions"]
    C --> D{"Inventory empty?"}
    D -->|"Yes"| E["Closed"]
    D -->|"No, auction expires"| F["Return remainder"]
    A -->|"No fills"| G["Seller cancellation"]
Loading

Each fill transfers the buy token and protocol fee into the auction house, then atomically pays the seller recipient and releases sell inventory to the solver.

Features

  • Linear Dutch price decay.
  • Partial fills and buyer-side maximum-cost protection.
  • Normalized prices across ERC-20 tokens with up to 18 decimals.
  • Optional fresh-oracle deviation floor.
  • Protocol fee snapshot per auction.
  • Permissionless expiry closure and seller-only pre-fill cancellation.
  • Pause blocks new risk while cancellation and expiry closure remain available.
  • Exact escrow accounting by sell token.

Security properties

For each auction:

totalSell == soldAmount + remainingSell + returnedAmount

Across auctions sharing a token:

sellToken.balanceOf(auctionHouse) == escrowedSellBalance[sellToken]

The invariant test suite continuously checks both properties across randomized partial fills.

See SPEC.md and THREAT_MODEL.md.

Development

forge install --no-git --shallow \
  OpenZeppelin/openzeppelin-contracts@v5.6.1 \
  foundry-rs/forge-std@v1.16.2
forge fmt --check
forge test -vvv
forge lint

Pinned toolchain:

  • Foundry v1.7.1
  • Solidity 0.8.34
  • OpenZeppelin Contracts v5.6.1
  • forge-std v1.16.2

Price convention

Prices use 1e18 precision and represent whole buy tokens per whole sell token. The contract applies token decimals when calculating base-unit payment:

buyAmount =
  sellBaseUnits
  × priceX18
  × 10^buyDecimals
  ÷ 10^sellDecimals
  ÷ 1e18

Payment is rounded up in favor of the seller.

Deployment

forge script script/Deploy.s.sol:Deploy --rpc-url "$RPC_URL" --broadcast

Configure OWNER, FEE_RECIPIENT, and PROTOCOL_FEE_BPS from .env.example.

Base Sepolia demo

DeployDemo.s.sol atomically deploys two test tokens, a price oracle, and the auction house, then creates and fully fills an oracle-bounded Dutch auction:

export DEMO_DEPLOYER=0xYourDeployer
forge script script/DeployDemo.s.sol:DeployDemo \
  --rpc-url https://sepolia.base.org \
  --account portfolio-testnet \
  --broadcast

The atomic scenario prevents block-time drift between auction creation and its immediate smoke-test fill.

See the live Base Sepolia deployment record for verified addresses, the creation transaction, and final auction accounting.

Explicit limitations

  • Only standard, non-rebasing, non-fee-on-transfer ERC-20s are supported.
  • The external oracle is trusted to publish the correct normalized pair price.
  • The contract does not source liquidity or guarantee auction completion.
  • Updating the fee recipient affects where fees are paid; each auction snapshots only its fee rate.
  • This implementation is intentionally non-upgradeable.

License

MIT