Computing and Accumulating Interest On-chain
Austin Williams
Part I: Computing interest on-chain
How to compute interest in a land of integers
Simple interest
A = P + rtP
Simple interest
A = P + rtP
total Amount owed
Simple interest
A = P + rtP
total Amount owed
the Principal (original amount borrowed)
Simple interest
A = P + rtP
total Amount owed
the Principal (original amount borrowed)
the amount of Time that has passed. (units matter)
Simple interest
A = P + rtP
total Amount owed
the Principal (original amount borrowed)
the amount of Time that has passed (units matter)
the Rate at which interest accumulates per unit of time (not including any compounding). AKA “nominal interest rate” or “simple annual interest rate”.
Simple interest
A = P + rtP
the total amount of interest that has accrued
Simple interest
A = P (1+ rt)
factoring out P to get the more common expression
Compounding
Suppose you want to add the interest to the principal. And suppose you want to do this with a frequency of n times per unit of time.
Then the amount that you would owe after the first 1/n unit of time would be:
Compounding
Suppose you want to add the interest to the principal. And suppose you want to do this with a frequency of n times per unit of time.
Then the amount that you would owe after the first 1/n unit of time would be:
simplify
Compounding
Suppose you want to add the interest to the principal. And suppose you want to do this with a frequency of n times per unit of time.
Then the amount that you would owe after the first 1/n unit of time would be:
Compounding
Suppose you want to add the interest to the principal. And suppose you want to do this with a frequency of n times per unit of time.
Then the amount that you would owe after the first 1/n unit of time would be:
The amount that you would owe after the second 1/n unit of time would be:
Compounding
Suppose you want to add the interest to the principal. And suppose you want to do this with a frequency of n times per unit of time.
Then the amount that you would owe after the first 1/n unit of time would be:
The amount that you would owe after the second 1/n unit of time would be:
Compounding
Suppose you want to add the interest to the principal. And suppose you want to do this with a frequency of n times per unit of time.
Then the amount that you would owe after the first 1/n unit of time would be:
The amount that you would owe after the second 1/n unit of time would be:
The amount that you would owe after the third 1/n unit of time would be:
Compounding
Suppose you want to add the interest to the principal. And suppose you want to do this with a frequency of n times per unit of time.
Then the amount that you would owe after the first 1/n unit of time would be:
The amount that you would owe after the second 1/n unit of time would be:
The amount that you would owe after the third 1/n unit of time would be:
Compounding
Suppose you want to add the interest to the principal. And suppose you want to do this with a frequency of n times per unit of time.
Then the amount that you would owe after the first 1/n unit of time would be:
The amount that you would owe after the second 1/n unit of time would be:
The amount that you would owe after the third 1/n unit of time would be:
The amount that you would owe after the nth 1/n unit of time (that is, after 1 unit of time) would be:
Compounding
The amount that you would owe after the nth 1/n unit of time (that is, after 1 unit of time) would be:
Compounding
So the amount that you would owe after t units of time would be:
The amount that you would owe after the nth 1/n unit of time (that is, after 1 unit of time) would be:
Compounding
So the amount that you would owe after t units of time would be:
Two quick notes:
Compounding
Total amount owed after after t units of time on a loan with an initial principal amount P, earning interest at a nominal rate r, and compounding n times per unit of time:
Continuous Compounding
Total amount owed after after t units of time on a loan with an initial principal amount P, earning interest at a nominal rate r, and compounding n times per unit of time:
Continuous Compounding
Total amount owed after after t units of time on a loan with an initial principal amount P, earning interest at a nominal rate r, and compounding n times per unit of time:
“Euler’s number” (base of natural log) ≈ 2.71828
Continuous Compounding
Total amount owed after after t units of time on a loan with an initial principal amount P, earning interest at a nominal rate r, and compounding n times per unit of time:
Total amount owed after after t units of time on a loan with an initial principal amount P, earning interest at a nominal rate r, and compounding continuously:
Continuous Compounding
Total amount owed after after t units of time on a loan with an initial principal amount P, earning interest at a nominal rate r, and compounding n times per unit of time:
Total amount owed after after t units of time on a loan with an initial principal amount P, earning interest at a nominal rate r, and compounding continuously:
Goal: compute these values on-chain...
Challenges
Challenges
Example: �
Challenges
Example: �
uint256
float
float
float
Challenges
Example: �
uint256
float
float
float
Non-integer exponents 😬.
Very expensive to compute.
Can we do better? 🤔
Challenges
Example: �
uint256
float
float
uint256
Measure time in seconds.
Write nominal interest in seconds.
Challenges
Example: �
uint256
float
float
uint256
One of the factors in the exponent is now a uint256, but the other is still a float. 😫
😔
😊
Challenges
Example: �
uint256
float
float
uint256
One of the factors in the exponent is now a uint256, but the other is still a float. 😫
😔
😊
Challenges
Example: �
uint256
float
float
uint256
(Side note: The fact that e is irrational is not part of the problem. The only problem is that the exponent is not a non-negative integer)
Challenges
Total amount owed after after t units of time on a loan with an initial principal amount P, earning interest at a nominal rate r, and compounding n times per unit of time:
Total amount owed after after t units of time on a loan with an initial principal amount P, earning interest at a nominal rate r, and compounding continuously:
?
Challenges
Example: �
uint256
float
float
uint256
Challenges
Example: �
uint256
float
float
uint256
Again, we have a non-integer in the exponent. Let’s try measuring time in seconds again...
Challenges
Example: �
uint256
float
uint256
Challenges
Example: �
uint256
float
uint256
Integer exponent! 🎉
Challenges
Example: �
uint256
float
uint256
Integer exponent! 🎉
Bonus points for eliminating a division
Challenges
Total amount owed after after t units of time on a loan with an initial principal amount P, earning interest at a nominal rate r, and compounding n times per unit of time:
✓
Challenges
Total amount owed after after t units of time on a loan with an initial principal amount P, earning interest at a nominal rate r, and compounding n times per unit of time:
✓
Measure time in seconds, compound once per second, and we get something very close to continuous compounding that can be computed efficiently on-chain:
Challenges
Total amount owed after after t units of time on a loan with an initial principal amount P, earning interest at a nominal rate r, and compounding n times per unit of time:
✓
Measure time in seconds, compound once per second, and we get something very close to continuous compounding that can be computed efficiently on-chain:
The difference between this approach and actual/proper continuous compounding is less than 9 cents for a 1 year loan of $1M at a nominal interest rate of 10%/year. That’s very good!
Efficiency
An arbitrary fixed-precision base can be raised to a non-negative integer exponent, t, using O(log(t)) fixed-point multiplications by using the “exponentiation by squaring” algorithm.
Efficiency
An arbitrary fixed-precision base can be raised to a non-negative integer exponent, t, using O(log(t)) fixed-point multiplications by using the “exponentiation by squaring” algorithm.
This is how Maker computes interest. They implement the “exponentiation by squaring” algorithm in the rpow function of their Jug and Pot contracts.
“Manual Method”
You can also compute compound interest “manually” as we alluded to earlier.
A = P (1+ rt)
The idea is to update the amount owed using the simple interest formula once per block (or as close to that as possible).
“Manual Method”
You can also compute compound interest “manually” as we alluded to earlier.
A = P (1+ rt)
The idea is to update the amount owed using the simple interest formula once per block (or as close to that as possible).
This has the effect of compounding once every 15 seconds, assuming you update every block.
Plenty accurate if updated frequently. If updated every block, the difference between “manual compounding” and continuous compounding of a 1 year, $1M loan with a 10% nominal interest rate is, again, less than 9 cents.
“Manual Method”
You can also compute compound interest “manually” as we alluded to earlier.
A = P (1+ rt)
The idea is to update the amount owed using the simple interest formula once per block (or as close to that as possible).
This has the effect of compounding once every 15 seconds, assuming you update every block.
Plenty accurate if updated frequently. If updated every block, the difference between “manual compounding” and continuous compounding of a 1 year, $1M loan with a 10% nominal interest rate is, again, less than 9 cents.
This is how Compound computes interest.
Comparison
“Manual” Compounding
Comparison
“Manual” Compounding
X
✓
Comparison
“Manual” Compounding
X
X
✓
✓
Comparison
“Manual” Compounding
X
X
X
✓
✓
✓
Comparison
“Manual” Compounding
X
X
X
✓
✓
✓
✓
X
Comparison
“Manual” Compounding
X
X
X
✓
✓
✓
✓
X
No clear winner. Both are good.
Part II: Accumulating interest
How to accumulate interest for several users at once
The problem
Given all of this, how do we go about tracking all of the interest from all of our loans accurately and efficiently?
IOUs
Very common solution is to use “IOUs” and update the exchange rate between IOUs and their underlying token.
IOUs
Very common solution is to use “IOUs” and update the exchange rate between IOUs and their underlying token.
Global var: r (interest rate)
Global var: exchangeRate (between IOUs and the underlying token).
IOUs
Very common solution is to use “IOUs” and update the exchange rate between IOUs and their underlying token.
Global var: r (interest rate)
Global var: exchangeRate (between IOUs and the underlying token).
init exchangeRate = 1 when contract is deployed.
Before any action that looks at the exchange rate, update the exchangeRate variable via:
exchangeRate = exchangeRate(1+r)^t.
IOUs
Very common solution is to use “IOUs” and update the exchange rate between IOUs and their underlying token.
Global var: r (interest rate)
Global var: exchangeRate (between IOUs and the underlying token).
init exchangeRate = 1 when contract is deployed.
Before any action that looks at the exchange rate, update the exchangeRate variable via:
exchangeRate = exchangeRate(1+r)^t.
This is how it is done by:�
IOUs
Very common solution is to use “IOUs” and update the exchange rate between IOUs and their underlying token.
Global var: r (interest rate)
Global var: exchangeRate (between IOUs and the underlying token).
init exchangeRate = 1 when contract is deployed.
Before any action that looks at the exchange rate, update the exchangeRate variable via:
exchangeRate = exchangeRate(1+r)^t.
Contract
Example: Accumulating interest while lending...
IOUs
Very common solution is to use “IOUs” and update the exchange rate between IOUs and their underlying token.
Global var: r (interest rate)
Global var: exchangeRate (between IOUs and the underlying token).
init exchangeRate = 1 when contract is deployed.
Before any action that looks at the exchange rate, update the exchangeRate variable via:
exchangeRate = exchangeRate(1+r)^t.
Contract
Deposits X tokens
Example: Accumulating interest while lending...
IOUs
Very common solution is to use “IOUs” and update the exchange rate between IOUs and their underlying token.
Global var: r (interest rate)
Global var: exchangeRate (between IOUs and the underlying token).
init exchangeRate = 1 when contract is deployed.
Before any action that looks at the exchange rate, update the exchangeRate variable via:
exchangeRate = exchangeRate(1+r)^t.
Contract
Deposits X tokens
Receives
X / exchangeRate
IOUs
X / exchangeRate
IOUs
1
1
Example: Accumulating interest while lending...
IOUs
Very common solution is to use “IOUs” and update the exchange rate between IOUs and their underlying token.
Global var: r (interest rate)
Global var: exchangeRate (between IOUs and the underlying token).
init exchangeRate = 1 when contract is deployed.
Before any action that looks at the exchange rate, update the exchangeRate variable via:
exchangeRate = exchangeRate(1+r)^t.
Contract
X / exchangeRate IOUs
Time passes….
exchangeRate var gets updated (increases) over time...
1
Example: Accumulating interest while lending...
IOUs
Very common solution is to use “IOUs” and update the exchange rate between IOUs and their underlying token.
Global var: r (interest rate)
Global var: exchangeRate (between IOUs and the underlying token).
init exchangeRate = 1 when contract is deployed.
Before any action that looks at the exchange rate, update the exchangeRate variable via:
exchangeRate = exchangeRate(1+r)^t.
Contract
Sends the
X / exchangeRate
IOUs back to the contract
1
Example: Accumulating interest while lending...
IOUs
Very common solution is to use “IOUs” and update the exchange rate between IOUs and their underlying token.
Global var: r (interest rate)
Global var: exchangeRate (between IOUs and the underlying token).
init exchangeRate = 1 when contract is deployed.
Before any action that looks at the exchange rate, update the exchangeRate variable via:
exchangeRate = exchangeRate(1+r)^t.
Contract
Sends the
X / exchangeRate
IOUs back to the contract
1
tokens
tokens
Receives
Example: Accumulating interest while lending...
IOUs
Very common solution is to use “IOUs” and update the exchange rate between IOUs and their underlying token.
Global var: r (interest rate)
Global var: exchangeRate (between IOUs and the underlying token).
init exchangeRate = 1 when contract is deployed.
Before any action that looks at the exchange rate, update the exchangeRate variable via:
exchangeRate = exchangeRate(1+r)^t.
Contract
tokens
Example: Accumulating interest while lending...
Done!
Notice that we never had to record the times when the loan started or stopped.��The exchange rate was not account-specific.
This could have been any user at any time over any duration.
IOUs
Very common solution is to use “IOUs” and update the exchange rate between IOUs and their underlying token.
Global var: r (interest rate)
Global var: exchangeRate (between IOUs and the underlying token).
init exchangeRate = 1 when contract is deployed.
Before any action that looks at the exchange rate, update the exchangeRate variable via:
exchangeRate = exchangeRate(1+r)^t.
Contract
Example: Accumulating interest while borrowing...
Start off with X IOUs
(acquired by first putting depositing some token, just like the lender did in the previous example)
IOUs
Very common solution is to use “IOUs” and update the exchange rate between IOUs and their underlying token.
Global var: r (interest rate)
Global var: exchangeRate (between IOUs and the underlying token).
init exchangeRate = 1 when contract is deployed.
Before any action that looks at the exchange rate, update the exchangeRate variable via:
exchangeRate = exchangeRate(1+r)^t.
Contract
Example: Accumulating interest while borrowing...
Sends the
X IOUs to the contract to be “locked”
IOUs
Very common solution is to use “IOUs” and update the exchange rate between IOUs and their underlying token.
Global var: r (interest rate)
Global var: exchangeRate (between IOUs and the underlying token).
init exchangeRate = 1 when contract is deployed.
Before any action that looks at the exchange rate, update the exchangeRate variable via:
exchangeRate = exchangeRate(1+r)^t.
Contract
Example: Accumulating interest while borrowing...
Sends the
X IOUs to the contract to be “locked”
Allowed to borrow up to
tokens, where c is determined by the max LTV ratio
tokens
IOUs
Very common solution is to use “IOUs” and update the exchange rate between IOUs and their underlying token.
Global var: r (interest rate)
Global var: exchangeRate (between IOUs and the underlying token).
init exchangeRate = 1 when contract is deployed.
Before any action that looks at the exchange rate, update the exchangeRate variable via:
exchangeRate = exchangeRate(1+r)^t.
Contract
Example: Accumulating interest while borrowing...
tokens
Time passes….
exchangeRate var gets updated (increases) over time...
IOUs
Very common solution is to use “IOUs” and update the exchange rate between IOUs and their underlying token.
Global var: r (interest rate)
Global var: exchangeRate (between IOUs and the underlying token).
init exchangeRate = 1 when contract is deployed.
Before any action that looks at the exchange rate, update the exchangeRate variable via:
exchangeRate = exchangeRate(1+r)^t.
Contract
Example: Accumulating interest while borrowing...
tokens
Sends
tokens back to the contract
IOUs
Very common solution is to use “IOUs” and update the exchange rate between IOUs and their underlying token.
Global var: r (interest rate)
Global var: exchangeRate (between IOUs and the underlying token).
init exchangeRate = 1 when contract is deployed.
Before any action that looks at the exchange rate, update the exchangeRate variable via:
exchangeRate = exchangeRate(1+r)^t.
Contract
Example: Accumulating interest while borrowing...
tokens
Sends
tokens back to the contract
IOUs
Very common solution is to use “IOUs” and update the exchange rate between IOUs and their underlying token.
Global var: r (interest rate)
Global var: exchangeRate (between IOUs and the underlying token).
init exchangeRate = 1 when contract is deployed.
Before any action that looks at the exchange rate, update the exchangeRate variable via:
exchangeRate = exchangeRate(1+r)^t.
Contract
Example: Accumulating interest while borrowing...
Sends
tokens back to the contract
Contract unlocks the original X IOUs
X IOUs
IOUs
Very common solution is to use “IOUs” and update the exchange rate between IOUs and their underlying token.
Global var: r (interest rate)
Global var: exchangeRate (between IOUs and the underlying token).
init exchangeRate = 1 when contract is deployed.
Before any action that looks at the exchange rate, update the exchangeRate variable via:
exchangeRate = exchangeRate(1+r)^t.
Contract
Example: Accumulating interest while borrowing...
X IOUs
Done!
Again, no times needed to be recorded.��The exchange rate was not account-specific.
Could have had hundreds of these happening at once, and still only need to update one var: exchangeRate
IOUs
Benefits:
IOUs
Benefits:
Limitations:�
Quick Security Notes ⚠️
Possible Opportunities
The End