1/5
## Implementing Interest Accrual and Burning in a Rebase Token This lesson explores two essential functions within a rebase token smart contract: `_mintAccruedInterest` and `burn`. We'll delve into how to ensure users receive their earned interest correctly before their balance changes and how to handle token burning, particularly full withdrawals, using common DeFi patterns. This builds upon a standard ERC20 token structure, adding custom logic for linear interest accrual. **Understanding Rebase Tokens and Balance Dynamics** Before diving into the functions, let's recap key concepts: * **Rebase Token:** A type of ERC20 token where a user's balance automatically increases over time based on a predefined mechanism, like interest accrual. * **Principal Balance:** The amount of tokens explicitly minted or transferred to a user. This is the value stored in the base ERC20 `_balances` mapping (accessible via `super.balanceOf`). * **Actual Balance:** The user's principal balance *plus* any interest that has accrued since their last balance update event (like a mint, burn, or transfer). Our contract calculates this on-the-fly using an overridden `balanceOf` function that factors in the time elapsed and the user's specific interest rate. The core challenge is ensuring that the *principal balance* accurately reflects the *actual balance* before any operation that relies on or modifies this principal balance. This is where `_mintAccruedInterest` comes in. **The `_mintAccruedInterest` Function: Synchronizing Balances** The `_mintAccruedInterest` function is a crucial internal helper. Its sole purpose is to calculate any interest a user has earned since their last interaction and mint those tokens, effectively updating their principal balance to match their actual, interest-inclusive balance. It's marked `internal` because it's not intended for direct external calls but rather as a prerequisite step within other functions like `mint`, `burn`, and `transfer`. **Implementation Steps:** 1. **Get Previous Principal Balance:** We first retrieve the user's balance as currently recorded by the standard ERC20 logic using `super.balanceOf(_user)`. This represents the tokens already minted to them. 2. **Get Current Actual Balance:** Next, we call our contract's *overridden* `balanceOf(_user)` function. This function performs the interest calculation, returning the principal plus newly accrued interest. 3. **Calculate Interest to Mint:** The difference between the `currentBalance` (actual) and the `previousPrincipleBalance` represents the interest (`balanceIncrease`) that has accrued and needs to be minted. 4. **Update Last Timestamp (Effect):** Critically, we update the user's last interaction timestamp (`s_userLastUpdatedTimestamp[_user] = block.timestamp;`). Following the **Checks-Effects-Interactions** pattern, we modify the contract's state *before* performing external calls or interactions (like minting) to prevent potential reentrancy issues. 5. **Mint the Interest (Interaction):** Finally, we call the internal `_mint(_user, balanceIncrease)` function inherited from OpenZeppelin's ERC20 contract. This issues the calculated `balanceIncrease` amount of tokens to the user, updating their principal balance in the `_balances` mapping. The `_mint` function itself emits the standard ERC20 `Transfer` event (from the zero address). ```solidity /** * @notice Mint the accrued interest to the user since the last time they interacted with the protocol (e.g. burn, mint, transfer) * @param _user The user to mint the accrued interest to */ function _mintAccruedInterest(address _user) internal { // (1) find their current balance of rebase tokens that have been minted to the user -> principle balance uint256 previousPrincipleBalance = super.balanceOf(_user); // (2) calculate their current balance including any interest -> balanceOf uint256 currentBalance = balanceOf(_user); // calculate the number of tokens that need to be minted to the user -> (2) - (1) uint256 balanceIncrease = currentBalance - previousPrincipleBalance; // set the users last updated timestamp (Effect) s_userLastUpdatedTimestamp[_user] = block.timestamp; // mint the user the balance increase (Interaction) _mint(_user, balanceIncrease); } ``` By calling `_mintAccruedInterest` at the beginning of any function that modifies a user's balance (like `mint` or `burn`), we ensure all calculations and actions are based on their fully realized, up-to-date token holdings. **The `burn` Function: Handling Withdrawals and Redemptions** The `burn` function allows users (or often, a managing contract like a vault) to destroy their tokens. This is typically used for withdrawals, redemptions, or potentially in cross-chain bridging mechanisms (burn-and-mint). A key consideration here is handling "dust interest" – the small amount of interest that might accrue between when a user decides to withdraw their *entire* balance and when the transaction actually executes on-chain. To address this cleanly, we implement the "Max Uint Pattern". **Implementation Steps:** 1. **Handle Max Uint Case:** We check if the requested `_amount` to burn is equal to `type(uint256).max`. This special value signals the user's intent to burn their entire balance. If detected, we ignore the provided `_amount` and instead fetch the user's *current actual balance* by calling `balanceOf(_from)`. This ensures that any dust interest accrued up to the moment of execution is included in the burn amount, allowing for a complete withdrawal. 2. **Mint Accrued Interest:** Before burning, we *must* call `_mintAccruedInterest(_from)`. This updates the user's principal balance to include any interest earned up to this point, ensuring the subsequent burn operation targets the correct, up-to-date principal amount. 3. **Burn Tokens:** We call the internal `_burn(_from, _amount)` function (inherited from OpenZeppelin ERC20). This decreases the user's principal balance by the specified `_amount` (which might have been updated in step 1) and emits the standard ERC20 `Transfer` event (to the zero address). ```solidity /** * @notice Burn the user tokens when they withdraw from the vault * @param _from The user to burn the tokens from * @param _amount The amount of tokens to burn */ function burn(address _from, uint256 _amount) external { // Note: Access control should be added // Check if user wants to burn entire balance if (_amount == type(uint256).max) { // Update amount to current full balance including interest _amount = balanceOf(_from); } // Update balance with accrued interest first _mintAccruedInterest(_from); // Burn the specified (potentially updated) amount _burn(_from, _amount); } ``` **Key Relationships and Considerations:** * **Interdependency:** `burn` (and other state-changing functions like `mint`) rely heavily on `_mintAccruedInterest` to maintain balance integrity. * **Timestamp Updates:** The `_mintAccruedInterest` function's update to `s_userLastUpdatedTimestamp` is vital for accurate future interest calculations performed by the overridden `balanceOf` function. * **Event Emission:** We leverage the `Transfer` events emitted by the underlying `_mint` (within `_mintAccruedInterest`) and `_burn` (within `burn`) functions for off-chain tracking and indexing. No additional events are strictly necessary in these specific functions. * **Max Uint Pattern:** Using `type(uint256).max` is a common and effective DeFi pattern for handling full balance operations, ensuring users can completely exit their positions without leaving dust behind. This pattern is seen in established protocols like Aave V3's aTokens. * **Access Control:** As noted in the code comments, proper access control (e.g., using modifiers like `onlyOwner` or role-based access) should be added to functions like `burn` and `mint` to restrict who can call them, depending on the contract's specific requirements. By implementing `_mintAccruedInterest` and `burn` as described, we create a robust rebase token system where user balances accurately reflect accrued interest, and withdrawals can be handled cleanly and completely.
A crucial look into Implementing Interest Accrual and Burning in a Rebase Token - Understand the necessity of synchronizing principal and actual balances via `_mintAccruedInterest` in rebase tokens, and learn to implement the `burn` function, leveraging the Max Uint Pattern for complete withdrawals.
Previous lesson
Previous
Next lesson
Next
Give us feedback
Course Overview
About the course
Advanced smart contract development
How to develop a stablecoin
How to develop a DeFi protocol
How to develop a DAO
Advanced smart contracts testing
Fuzz testing
Manual verification
Web3 Developer Relations
$85,000 - $125,000 (avg. salary)
Web3 developer
$60,000 - $150,000 (avg. salary)
Smart Contract Engineer
$100,000 - $150,000 (avg. salary)
Smart Contract Auditor
$100,000 - $200,000 (avg. salary)
Security researcher
$49,999 - $120,000 (avg. salary)
Guest lecturers:
Juliette Chevalier
Lead Developer relations at Aragon
Nader Dabit
Director of developer relations at EigenLayer
Ally Haire
Developer relations at Protocol Labs
Harrison
Founder at GasliteGG
Last updated on April 4, 2025
Solidity Developer
Advanced FoundryDuration: 36min
Duration: 3h 06min
Duration: 5h 02min
Duration: 6h 02min
Duration: 2h 47min
Duration: 1h 23min
Duration: 4h 28min
Duration: 1h 19min
Duration: 1h 10min
Course Overview
About the course
Advanced smart contract development
How to develop a stablecoin
How to develop a DeFi protocol
How to develop a DAO
Advanced smart contracts testing
Fuzz testing
Manual verification
Web3 Developer Relations
$85,000 - $125,000 (avg. salary)
Web3 developer
$60,000 - $150,000 (avg. salary)
Smart Contract Engineer
$100,000 - $150,000 (avg. salary)
Smart Contract Auditor
$100,000 - $200,000 (avg. salary)
Security researcher
$49,999 - $120,000 (avg. salary)
Guest lecturers:
Juliette Chevalier
Lead Developer relations at Aragon
Nader Dabit
Director of developer relations at EigenLayer
Ally Haire
Developer relations at Protocol Labs
Harrison
Founder at GasliteGG
Last updated on April 4, 2025
Testimonials
Read what our students have to say about this course.
Chainlink
Chainlink
Gustavo Gonzalez
Solutions Engineer at OpenZeppelin
Francesco Andreoli
Lead Devrel at Metamask
Albert Hu
DeForm Founding Engineer
Radek
Senior Developer Advocate at Ceramic
Boidushya
WalletConnect
Idris
Developer Relations Engineer at Axelar