$RENT on Pons. Contract address: launch soon Follow @rentdayxyz for the address
Rentday
CAsoon

Contracts

Two small contracts: a vault that holds USDG and a registry that records each Roll.

Overview

Rentday runs on two Solidity contracts, both built on OpenZeppelin Contracts 5 and compiled with Solidity 0.8.28. The source is in contracts/src, the exported ABIs in contracts/abi and the tests in contracts/test. contracts/README.md documents the package: building, testing, deploying and the operator scripts. Neither contract is upgradeable. Both addresses read Launch soon on Chain and addresses until deployment.

RentdayVault

An OpenZeppelin ERC-4626 vault over USDG. The share token is named Rentday Vault Share, with the symbol vRENT and 18 decimals.

FeatureBehaviour
Constructorconstructor(usdg, initialOwner, initialCap). Reads the asset's decimals() and reverts with RentdayVaultInvalidAssetDecimals unless it is 6. Emits CapUpdated(0, initialCap).
totalAssets()The vault's USDG balance, the OpenZeppelin default. Nobody pushes a valuation in.
Decimals offset_decimalsOffset() returns 12, so shares have 6 + 12 = 18 decimals and 10^12 virtual shares stand against one virtual unit of USDG. Yield arrives as plain transfers, so donations to the vault are possible by design. The offset makes a first-depositor or donation inflation attack cost the attacker far more than it can extract.
Deposit capcap() returns the cap in USDG base units, compared with totalAssets(). setCap(uint256) is owner-only and emits CapUpdated(previousCap, newCap).
Pausepause() and unpause() are owner-only. While paused, maxDeposit and maxMint return 0, so deposit and mint revert with ERC4626ExceededMaxDeposit and ERC4626ExceededMaxMint. redeem, withdraw, share transfers and addCapital keep working.
addCapital(uint256 amount)Callable by anyone. Pulls amount USDG from the caller, which needs a USDG approval first, and emits CapitalAdded(from, amount). Reverts with RentdayVaultZeroAmount for 0, and with RentdayVaultNoShares while no vRENT exists, because USDG added to an empty vault would be captured by the virtual shares. A plain USDG transfer has the same effect on the price without the event.
Withdrawal pathsNone for the owner. USDG leaves only through redeem or withdraw, called by a holder or by someone the holder approved.
OwnershipOwnable2Step: the owner calls transferOwnership(address) and the new owner calls acceptOwnership(). renounceOwnership() always reverts with RentdayVaultRenounceDisabled.
maxMint(address)Returns the uint256 maximum instead of reverting if the cap is set so high that the conversion would overflow, as ERC-4626 requires of its max functions.

RollRegistry

An append-only record of Rolls, numbered from 1. Each entry is stored as:

struct Roll {
    bytes32 docHash;      // SHA-256 of the exact CSV bytes
    uint256 navPerShare;  // USDG per vRENT, 12 decimals
    string  uri;          // where the file is published
    uint64  at;           // block timestamp of publication
}
Function or eventBehaviour
rolls(uint16 n) returns (bytes32 docHash, uint256 navPerShare, string uri, uint64 at)Returns Roll n in that order. An unpublished number returns zeros, with at equal to 0.
latest() returns (uint16)The number of the most recent Roll, or 0 before the first.
publish(uint16 n, bytes32 docHash, uint256 navPerShare, string uri)Owner only. n must equal latest() + 1, or the call reverts with RollRegistryUnexpectedRollNumber(expected, provided). A zero docHash reverts with RollRegistryZeroDocHash. Stores the Roll with the block timestamp and emits RollPublished.
event RollPublished(uint16 indexed n, bytes32 docHash, uint256 navPerShare, string uri, uint64 at)Emitted once per Roll.

The registry has no function to change or delete a Roll. Ownership is Ownable2Step, and renounceOwnership() reverts with RollRegistryRenounceDisabled, since a registry without an owner could never publish again. Roll numbers are uint16, which allows up to 65,535 Rolls.

Functions the site calls

The site encodes its calls itself, without a library, in site/js/abi.js. Its SIG table holds these signatures. Each selector is the first four bytes of the keccak-256 hash of the signature, as listed in contracts/abi/selectors.json.

SignatureSelectorContractUsed for
balanceOf(address)0x70a08231USDG, RentdayVaultYour USDG and vRENT balances
allowance(address,address)0xdd62ed3eUSDGYour current approval to the vault
approve(address,uint256)0x095ea7b3USDGExact-amount approval before a deposit
decimals()0x313ce567RentdayVaultIdentity check (must be 18)
symbol()0x95d89b41RentdayVaultIdentity check
asset()0x38d52e0fRentdayVaultIdentity check (must be USDG)
totalAssets()0x01e1d114RentdayVaultUSDG held
totalSupply()0x18160dddRentdayVaultvRENT outstanding
convertToAssets(uint256)0x07a2d13aRentdayVaultShare price, called with 1e18
previewDeposit(uint256)0xef8b30f7RentdayVaultDeposit quote
previewRedeem(uint256)0x4cdad506RentdayVaultCash-out quote
maxDeposit(address)0x402d267dRentdayVaultRoom under the cap, 0 while paused
deposit(uint256,address)0x6e553f65RentdayVaultDeposit
redeem(uint256,address,address)0xba087652RentdayVaultCash out
cap()0x355274eaRentdayVaultDeposit cap
paused()0x5c975abbRentdayVaultPause flag
addCapital(uint256)0xbea10f96RentdayVaultSettling rent and token fees
rolls(uint16)0x514f5ab2RollRegistryRoll verification
latest()0x52bfe789RollRegistryLatest Roll number

The operator scripts also call publish(uint16,bytes32,uint256,string), selector 0x41c50a25. A test in the contracts package checks that every selector in selectors.json is present in the compiled bytecode.

Source and tests

cd contracts
npm install
npm test

Security describes what the tests cover. contracts/README.md is the reference for the package's scripts, and Operator guide shows them in use.