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.
| Feature | Behaviour |
|---|---|
| Constructor | constructor(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 cap | cap() returns the cap in USDG base units, compared with totalAssets(). setCap(uint256) is owner-only and emits CapUpdated(previousCap, newCap). |
| Pause | pause() 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 paths | None for the owner. USDG leaves only through redeem or withdraw, called by a holder or by someone the holder approved. |
| Ownership | Ownable2Step: 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 event | Behaviour |
|---|---|
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.
| Signature | Selector | Contract | Used for |
|---|---|---|---|
balanceOf(address) | 0x70a08231 | USDG, RentdayVault | Your USDG and vRENT balances |
allowance(address,address) | 0xdd62ed3e | USDG | Your current approval to the vault |
approve(address,uint256) | 0x095ea7b3 | USDG | Exact-amount approval before a deposit |
decimals() | 0x313ce567 | RentdayVault | Identity check (must be 18) |
symbol() | 0x95d89b41 | RentdayVault | Identity check |
asset() | 0x38d52e0f | RentdayVault | Identity check (must be USDG) |
totalAssets() | 0x01e1d114 | RentdayVault | USDG held |
totalSupply() | 0x18160ddd | RentdayVault | vRENT outstanding |
convertToAssets(uint256) | 0x07a2d13a | RentdayVault | Share price, called with 1e18 |
previewDeposit(uint256) | 0xef8b30f7 | RentdayVault | Deposit quote |
previewRedeem(uint256) | 0x4cdad506 | RentdayVault | Cash-out quote |
maxDeposit(address) | 0x402d267d | RentdayVault | Room under the cap, 0 while paused |
deposit(uint256,address) | 0x6e553f65 | RentdayVault | Deposit |
redeem(uint256,address,address) | 0xba087652 | RentdayVault | Cash out |
cap() | 0x355274ea | RentdayVault | Deposit cap |
paused() | 0x5c975abb | RentdayVault | Pause flag |
addCapital(uint256) | 0xbea10f96 | RentdayVault | Settling rent and token fees |
rolls(uint16) | 0x514f5ab2 | RollRegistry | Roll verification |
latest() | 0x52bfe789 | RollRegistry | Latest 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.