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 activePositionIdsInterest 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