ArchitectureData Structures

Data Structures

Detailed specifications for RevvFi’s on-chain data structures, sourced directly from the deployed contracts.


Position State (RevvFiMarket)

Rather than a single struct, each position’s state is split across parallel mappings keyed by position (token) ID — this is the core of the per-position independent accrual model:

mapping(uint256 => uint256) public positionPrincipal;         // actual owed principal
mapping(uint256 => uint256) public positionLastAccrualTime;   // last interest roll-up
mapping(uint256 => uint256) public positionApr;                // this position's own APR (bps)
mapping(uint256 => uint8)   public positionSeniority;           // 0 = senior, 1 = junior
mapping(uint256 => bool)    public positionActive;
mapping(uint256 => bool)    public positionSettled;
mapping(uint256 => uint256) public positionClaimableAmount;    // pull-based claim balance
mapping(uint256 => address) public settledPositionOwner;        // owner snapshot at settlement
 
uint256[] public activePositionIds;
mapping(uint256 => uint256) public activePositionIndex;         // O(1) removal from activePositionIds

Interest accrual for a given position is computed on demand, not via a stored global index:

owed = principal + (principal * apr * elapsed) / (BASIS_POINTS * SECONDS_PER_YEAR)

where elapsed = block.timestamp - positionLastAccrualTime[id]. On any repayment or loss event, accrued interest is rolled into positionPrincipal and positionLastAccrualTime is reset to block.timestamp.


Offer (RevvFiOfferBook)

struct Offer {
    uint256 id;
    address lender;
    uint256 amount;            // original offer size
    uint256 remainingAmount;    // decreases as the offer is (partially) filled
    uint256 apr;                // basis points
    uint8 seniority;            // 0 = senior, 1 = junior
    uint256 expiry;             // timestamp
    bool active;
}

Offers are bucketed internally by APR (aprBuckets) so getBestOffers() can scan lowest-APR-first without sorting the full offer list on every call.


Auction (RevvFiLiquidator)

struct Auction {
    uint256 id;
    address market;
    address borrower;
    address borrowAsset;        // bids are placed in this token
    address collateralAsset;    // token being sold
    uint256 collateralAmount;
    uint256 debtAmount;          // starting price = 100% of this
    uint256 reservePrice;        // 80% of debtAmount, floor price
    uint256 startTime;
    uint256 endTime;
    uint256 highestBid;
    address highestBidder;
    bool active;
    bool settled;
    bool collateralTransferred;
}

Current price decays in discrete steps rather than continuously:

steps = (block.timestamp - startTime) / dutchAuctionStepDuration;   // default: 1 hour
priceDecrement = debtAmount * dutchAuctionPriceDecrementBps * steps / 10000;  // default: 5% per step
currentPrice = max(debtAmount - priceDecrement, reservePrice);

A bid must meet currentPrice if it’s the first bid, or exceed the previous highestBid by minBidIncrementBps (1% default) otherwise. A bid placed inside the last auctionExtensionWindow (15 minutes default) pushes endTime forward by that same window.


BorrowerProfile (ReputationRegistry)

struct BorrowerProfile {
    uint256 successfulLoans;
    uint256 defaultedLoans;
    uint256 reputationScore;   // 0-1000, recalculated on every update
    uint256 lastUpdateTime;
}
function _calculateReputationScore(BorrowerProfile storage profile) internal view returns (uint256) {
    uint256 totalLoans = profile.successfulLoans + profile.defaultedLoans;
    if (totalLoans == 0) return 500;
 
    uint256 successRate = (profile.successfulLoans * 1000) / totalLoans;
    uint256 defaultPenalty = profile.defaultedLoans * 50;
 
    int256 score = int256(successRate) - int256(defaultPenalty);
    if (score <= 0) return 0;
    if (score >= 1000) return 1000;
    return uint256(score);
}

Market Registry (RevvFiArchController)

Registries are stored as EnumerableSet.AddressSet, not plain mappings — this gives O(1) add/remove/contains plus enumerability (.values(), paginated .at()) for free:

using EnumerableSet for EnumerableSet.AddressSet;
 
EnumerableSet.AddressSet internal _markets;             // every deployed market
EnumerableSet.AddressSet internal _borrowers;            // approved borrower addresses
EnumerableSet.AddressSet internal _assetBlacklist;        // blacklisted borrow/collateral assets/oracles
EnumerableSet.AddressSet internal _controllers;
EnumerableSet.AddressSet internal _controllerFactories;

A borrower isn’t limited to a single market — deployMarket() has no one-market-per-borrower restriction, so a registered borrower can have multiple independent markets, each isolated from the others.


Next Steps

  • Review Smart Contracts for the functions that read/write these structures
  • Check Reference → Events for how state changes surface on-chain
  • Explore Mechanics for how these structures interact during a loan’s lifecycle