Skip to content

Commit

Permalink
Rename OptIn/OptOut
Browse files Browse the repository at this point in the history
  • Loading branch information
DanielVF committed Nov 6, 2024
1 parent e579a91 commit fda9764
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 15 deletions.
87 changes: 80 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,96 @@
# OUSD Token: 4.0
# OUSD Token: Version 4.0

We are revamping the our rebasing token contract.

The primary objective is to allow delegated yield. Delegated yield an account to seemlessly transfer all earned yield to another account.
The primary objective is to allow delegated yield. Delegated yield allows an account to seemlessly transfer all earned yield to another account.

Secondarily, we'd like to fix the tiny rounding issues around transfers and between local account information and global tracking slots.


## How old OUSD works.
## How OUSD works.

OUSD is a rebasing token. It's mission in life is to be able to distribute increases in backing assets on to users by having user's balances go up each time the token rebases.

**`_rebasingCreditsPerToken`** is a global variable that converts between "credits" stored on a account, and the actual balance of the account. This allows this single variable to be updated and all "rebasing" users have their account balance change proportionally. Counterintuitively, this is not a multiplier on users credits, but a divider. So it's `user balance = user credits / _rebasingCreditsPerToken`. Because it's a divider, OUSD will slowly lose resolution over very long timeframes, as opposed to abruptly stoping working suddenly once enough yield has been earned.
**`_rebasingCreditsPerToken`** is a global variable that converts between "credits" stored on a account, and the actual balance of the account. This allows this single variable to be updated and in turn all "rebasing" users have their account balance change proportionally. Counterintuitively, this is not a multiplier on users credits, but a divider. So it's `user balance = user credits / _rebasingCreditsPerToken`. Because it's a divider, OUSD will slowly lose resolution over very long timeframes, as opposed to abruptly stopping working suddenly once enough yield has been earned.

**_creditBalances[account]** This account mapping stores the internal credits for each account.
**_creditBalances[account]** This per account mapping stores the internal credits for each account.

**nonRebasingCreditsPerToken[account]** This account mapping stores an alternative conversion factor for the value used in creditBalances.
**alternativeCreditsPerToken[account]** This per account mapping stores an alternative, optional conversion factor for the value used in creditBalances. When it is set to zero, it says that it is unused, and the global `_rebasingCreditsPerToken` should be used instead. Because this alternative conversion factor does not update on rebases, it allows an account to be "frozen" and no longer change balances as rebases happen.

## Default / Rebasing Account
**rebaseState[account]** This holds user preferences for what type of accounting is used on an account. For historical reasons the default, `NotSet` value on this could mean that the account is using either `StdRebasing` or `StdNonRebasing` accounting (see details later).

### StdRebasing Account (Default)

This is the "normal" account in the system. It gets yield and its balance goes up over time. Almost account is this type.

Reads:

- `rebaseState`: could be either `NotSet` or `StdRebasing`. Almost all accounts are `NotSet`, and typicly only contracts that want to receive yield are set to `StdRebasing` (though there's nothing preventing regular users making an from an explicitly marking their account as recieving yield).
- `alternativeCreditsPerToken`: will always be zero, thus using the global _rebasingCreditsPerToken
- `_creditBalances`: credits for the account

Writes:

- `rebaseState`: if explicitly moving to this state from another state `StdRebasing` is set. Otherwise, the account remains `NotSet`.
- `alternativeCreditsPerToken`: will always be zero
- `_creditBalances`: credits for the account

Transitions to:

- automatic conversion to a `StdNonRebasing` account if funds are moved to or from a contract* AND the account is currently `NotSet`.
- to `StdNonRebasing` if the account calls `rebaseOptOut()`
- to `YieldDelegationTarget` if it is the destinataion account in a `delegateYield()`

### StdNonRebasing Account (Default)

This account does not earn yield. It was orignaly created for backwards compatibility with systems that did not support balance changes, as well not wasting yield on third party contracts holding tokens that did not support any distribution to users. As a side benefit, regular users earn at a higher rate than the increase in assets.

Reads:

- `rebaseState`: could be either `NotSet` or `StdNonRebasing`. Historicaly, almost all accounts are `NotSet` and you can only determine which kind of account `NotSet` is by looking at `alternativeCreditsPerToken`.
- `alternativeCreditsPerToken` Will always be non-zero. Probably ranges from 1e17-ish to 1e27, with most at 1e27.
- `_creditBalances` will either be a "frozen credits style" that can be converted via `alternativeCreditsPerToken`, or "frozen balance" style, losslessly convertable via an 1e18 or 1e27 in `alternativeCreditsPerToken`.

Writes:

- `rebaseState`: Set to `StdNonRebasing` when new contracts are automaticly moved to this state, or when explicitly converted to this account type. This was not previously the case for historical automatic conversions.
- `alternativeCreditsPerToken`: New balance writes will always use 1e18, which will result in the account's credits being equal to the balance.
- `_creditBalances`: New balance writes will always use 1:1 a credits/balance ratio, which will make this be the account balance.

Transitions to:

- to `StdRebasing` via a `rebaseOptIn()` call or a governance `governanceRebaseOptIn()`.
- to `YieldDelegationSource` if the source account in a `delegateYield()` call


### YieldDelegationSource

This account does not earn yield, instead its yield is passed on to another account.

It does this by keeping a non-rebasing style fixed balance locally, while storing all its rebasing credits on the target account. This makes the target account's credits be `(target account's credits + source account's credits)`

Reads / Writes (no historical accounts to deal with!):

- `rebaseState`: `YieldDelegationSource`
- `alternativeCreditsPerToken`: Always 1e18.
- `_creditBalances`: Always set to the account balance in 1:1 credits.
- Target account's `_creditBalances`: Increased by this accounts credits at the global `_rebasingCreditsPerToken`.

Transitions to:
- to `StdNonRebasing` if `undelegateYield()` is called on the yield delegation

### YieldDelegationTarget

This account earns extra yield from exactly one account. YieldDelegationTargets can have their own balances, and these balances to do earn. This works by having both account's credits stored in this account, but then subtracting the other account's fixed balance from the total.

For example, someone loans you an intrest free $10,000. You now have an extra $10,000, but also owe them $10,000 so that nets out to a zero change in your wealth. You take that $10,000 and invest it in T-bills, so you are now getting more yield than you did before.


Reads / Writes (no historical accounts to deal with!):
- `rebaseState`: `YieldDelegationTarget`
- `alternativeCreditsPerToken`: Always 0
- `_creditBalances`: The sum of this account's credits and the yield sources credits.
- Source account's `_creditBalances`: This balance is subtracted by that value

Transitions to:
- to `StdRebasing` if `undelegateYield()` is called on the yield delegation
17 changes: 9 additions & 8 deletions src/token/OUSD.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ contract OUSD is Governable {

enum RebaseOptions {
NotSet,
OptOut,
OptIn,
StdNonRebasing,
StdRebasing,
YieldDelegationSource,
YieldDelegationTarget
}
Expand Down Expand Up @@ -191,6 +191,7 @@ contract OUSD is Governable {
);
}

// Backwards compatible view
function nonRebasingCreditsPerToken(address _account) external view returns (uint256) {
return alternativeCreditsPerToken[_account];
}
Expand Down Expand Up @@ -475,7 +476,7 @@ contract OUSD is Governable {
uint256 balance = balanceOf(msg.sender);

// Account
rebaseState[msg.sender] = RebaseOptions.OptIn;
rebaseState[msg.sender] = RebaseOptions.StdRebasing;
alternativeCreditsPerToken[msg.sender] = 0;
_creditBalances[msg.sender] = _balanceToRebasingCredits(balance);

Expand All @@ -498,7 +499,7 @@ contract OUSD is Governable {
uint256 balance = balanceOf(_account);

// Account
rebaseState[_account] = RebaseOptions.OptOut;
rebaseState[_account] = RebaseOptions.StdNonRebasing;
alternativeCreditsPerToken[_account] = 1e18;
_creditBalances[_account] = balance;

Expand Down Expand Up @@ -559,8 +560,8 @@ contract OUSD is Governable {
, "Blocked by existing yield delegation");
RebaseOptions stateFrom = rebaseState[from];
RebaseOptions stateTo = rebaseState[to];
require(_isNonRebasingAccount(from) && (stateFrom == RebaseOptions.NotSet || stateFrom == RebaseOptions.OptOut), "Must delegate from a non-rebasing account");
require(!_isNonRebasingAccount(to) && (stateTo == RebaseOptions.NotSet || stateTo == RebaseOptions.OptIn), "Must delegate to a rebasing account");
require(_isNonRebasingAccount(from) && (stateFrom == RebaseOptions.NotSet || stateFrom == RebaseOptions.StdNonRebasing), "Must delegate from a non-rebasing account");
require(!_isNonRebasingAccount(to) && (stateTo == RebaseOptions.NotSet || stateTo == RebaseOptions.StdRebasing), "Must delegate to a rebasing account");

// Set up the bidirectional links
yieldTo[from] = to;
Expand Down Expand Up @@ -593,8 +594,8 @@ contract OUSD is Governable {
// Set up the bidirectional links
yieldFrom[yieldTo[from]] = address(0);
yieldTo[from] = address(0);
rebaseState[from] = RebaseOptions.OptOut;
rebaseState[to] = RebaseOptions.OptIn;
rebaseState[from] = RebaseOptions.StdNonRebasing;
rebaseState[to] = RebaseOptions.StdRebasing;

// Local
_creditBalances[from] = fromBalance;
Expand Down

0 comments on commit fda9764

Please sign in to comment.