Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Yield Delegation / Rebasing Token Rewrite #2298

Merged
merged 114 commits into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
114 commits
Select commit Hold shift + click to select a range
07293d2
initial second implementation of rebasing to another account
sparrowDom Oct 30, 2024
048b03f
update to core functionality
sparrowDom Oct 30, 2024
36c599e
add gas fucntion
sparrowDom Nov 3, 2024
05d746e
simplify execute
sparrowDom Nov 3, 2024
28d83f9
simplify mint and burn
sparrowDom Nov 3, 2024
45f476b
initial version of Daniel's yield delegation implementation
sparrowDom Nov 7, 2024
9994263
prettier and linter fixes
naddison36 Nov 7, 2024
4c22467
Add slots to OUSD contract to align with existing deployments
naddison36 Nov 7, 2024
f537b63
Generated new OUSD contract diagrams
naddison36 Nov 7, 2024
5743461
Added deploy scripts for OToken upgrades
naddison36 Nov 7, 2024
68ae87e
Generated new OUSD storage diagram
naddison36 Nov 7, 2024
2dbd19e
Merge remote-tracking branch 'origin/master' into sparrowDom/rebaseEl…
naddison36 Nov 7, 2024
58afd12
some minor bug fixes
sparrowDom Nov 7, 2024
17f121c
Prettier and fix spelling in comments
naddison36 Nov 7, 2024
381d13d
Unit test fixes
naddison36 Nov 7, 2024
4ee1b4b
fix initialise function sigature and visibility of functions
sparrowDom Nov 7, 2024
9a29c45
fix some tests
sparrowDom Nov 7, 2024
47e529f
add explanation for the alternativeCreditsPerToken check
sparrowDom Nov 7, 2024
24f099b
prettier
sparrowDom Nov 7, 2024
22c1cf5
reintroduce the original check
sparrowDom Nov 7, 2024
a5269b8
explicitly call rebaseOpt out if the yield delegating account hasn't …
sparrowDom Nov 8, 2024
e64ab17
add one more test
sparrowDom Nov 8, 2024
68d14bd
add Readme of the token contract logic
sparrowDom Nov 8, 2024
ef7c4ab
force account to be rebasing on yield delegation
sparrowDom Nov 8, 2024
90ec917
add explicit whitelist of allowed states when delegating yield
sparrowDom Nov 11, 2024
2c96306
add more defensive checks where isNonRebasingAccount can not mistanki…
sparrowDom Nov 13, 2024
4bf3360
remove increase/decrease allowance
sparrowDom Nov 13, 2024
5cbcf4c
Update docs with invarients
DanielVF Nov 13, 2024
0ab7bc8
Use safecast for any downcasting (#2306)
sparrowDom Nov 13, 2024
2029659
More invarients around outer functions
DanielVF Nov 13, 2024
5951660
Format invarients
DanielVF Nov 13, 2024
8c126c8
further simplify the code when handling different rebase states
sparrowDom Nov 13, 2024
8c2a358
remove nonreentrant modifiers from the toke code as they are not needed
sparrowDom Nov 13, 2024
d7a303f
allow empty conracts to rebaseOptIn without auto migration
sparrowDom Nov 15, 2024
2b3189f
Transfer unit tests for new token implementation (#2310)
sparrowDom Nov 15, 2024
aa53ea3
More invarients
DanielVF Nov 13, 2024
727e4e9
Correct total supply docs
DanielVF Nov 15, 2024
77d69f1
Transfer unit tests for new token implementation (#2310)
sparrowDom Nov 15, 2024
0cfa352
fix test
sparrowDom Nov 15, 2024
31c8078
remove redundant state checks
sparrowDom Nov 15, 2024
511dcaa
remove overwriting the same value to alternativeCreditsPerToken
sparrowDom Nov 15, 2024
5f63650
add non zero checks in delegation functions
sparrowDom Nov 15, 2024
4e327fa
correct error
sparrowDom Nov 15, 2024
9e9c726
correct some contract view modifiers
sparrowDom Nov 15, 2024
6e1b63e
add a readable error message when allowance is exceeded
sparrowDom Nov 15, 2024
659f294
reducing 2 functions to 1
sparrowDom Nov 15, 2024
f261756
deprecate isUpgraded
sparrowDom Nov 18, 2024
54b7ebe
rename totalSupply
sparrowDom Nov 18, 2024
2b31fd6
improve initialisation checks
sparrowDom Nov 18, 2024
108f0eb
remove gas optimisation that would also allow for errorneously large …
sparrowDom Nov 18, 2024
24865d0
remove underflow checks
sparrowDom Nov 18, 2024
b728d2e
simplify code check for rebaseOptIn and add a test for it
sparrowDom Nov 18, 2024
ba625f0
remove comments
sparrowDom Nov 18, 2024
f8280d1
move the balance and credits query above the rebaseState changes
sparrowDom Nov 18, 2024
ab460b8
use _adjustGlobals function to adjust globals in yield delegation
sparrowDom Nov 18, 2024
c94cd5f
futher simplify the undelegateYield function
sparrowDom Nov 18, 2024
c3684a5
unify the variable names
sparrowDom Nov 18, 2024
9cc74cc
add comment
sparrowDom Nov 18, 2024
2987362
a couple of more places to utilise the _adjustGlobals function
sparrowDom Nov 18, 2024
6b02387
no need this being a separate variable
sparrowDom Nov 18, 2024
6556835
undo bug introduction
sparrowDom Nov 18, 2024
56b11e2
wrong use of msg.sender bug fix
sparrowDom Nov 18, 2024
bbb3f45
function doesn't need to return any values
sparrowDom Nov 19, 2024
e949129
simplify
sparrowDom Nov 19, 2024
a1d3cdc
improve syntax
sparrowDom Nov 19, 2024
504a421
add events for yield delegation
sparrowDom Nov 19, 2024
546695f
remove var init
sparrowDom Nov 19, 2024
62c1045
fix deploy file
sparrowDom Nov 19, 2024
fd45920
add tests to catch possible incorrect rebaseOptIn / rebaseOptOut attr…
sparrowDom Nov 20, 2024
3d03c7b
simplify changeSupply code
sparrowDom Nov 20, 2024
fbb11a9
add storage slot gap
sparrowDom Nov 20, 2024
6d5c745
Comments update
DanielVF Nov 20, 2024
9ded07a
Comments spelling update
DanielVF Nov 20, 2024
2c6bdc2
correct comments
sparrowDom Nov 20, 2024
9436e7c
Merge remote-tracking branch 'origin/sparrowDom/rebaseElsewhere_v2' i…
sparrowDom Nov 20, 2024
f1939db
unify variable names
sparrowDom Nov 20, 2024
57c8733
make credits calculation based of off balance for higher accuracy (in…
sparrowDom Nov 20, 2024
dc803f2
minor gas optimisation
sparrowDom Nov 20, 2024
4893186
correct storage slot amount so it totals to 200
sparrowDom Nov 21, 2024
09bde1b
Improve rebasing supply accuracy V2 (#2314)
sparrowDom Nov 21, 2024
ae51bbd
gas optimisation
sparrowDom Nov 21, 2024
f03deb3
better naming
sparrowDom Nov 21, 2024
db2044a
add a test where multiple rebaseOptIn/OptOut calls do not result in i…
sparrowDom Nov 22, 2024
4495130
add a check for zero address with governanceRebaseOptIn tx
sparrowDom Nov 23, 2024
53db807
Update on rebasing
DanielVF Nov 25, 2024
948014c
add a check for zero address with governanceRebaseOptIn tx
sparrowDom Nov 23, 2024
7d7055c
Merge remote-tracking branch 'origin/sparrowDom/rebaseElsewhere_v2' i…
sparrowDom Nov 25, 2024
01b49a3
make an exception for balance exact non rebasing accounts (StdNonReba…
sparrowDom Nov 25, 2024
e355f3d
add test for the 1e27 cpt token exception
sparrowDom Nov 25, 2024
7636c99
add a test to for creditsBalanceOf and creditsBalanceOfHighres
sparrowDom Nov 25, 2024
da68531
add nonRebasingCreditsPerToken to the test
sparrowDom Nov 25, 2024
1b9d1e3
add auto migration test and revert test for rebaseOptOut
sparrowDom Nov 25, 2024
34021fb
prettier
sparrowDom Nov 26, 2024
57cccba
add tests for missing requires in yield delegation
sparrowDom Nov 26, 2024
24bf72a
simplify code
sparrowDom Nov 26, 2024
f1b5290
revert to the previous implementation of the deprecated function
sparrowDom Nov 26, 2024
20f1bd1
add OETH upgrade deployment file
sparrowDom Nov 28, 2024
7b76cb8
change license to Business Source License
sparrowDom Nov 29, 2024
dbb3434
prettier
sparrowDom Nov 29, 2024
93545c3
on changeSupply round up in the favour of the protocol
sparrowDom Nov 29, 2024
dc854bc
round down when calculating credits from balances
sparrowDom Dec 1, 2024
63ee2ba
Revert "round down when calculating credits from balances"
sparrowDom Dec 2, 2024
296f5f4
fix typos (#2323)
sparrowDom Dec 5, 2024
74bd138
gas optimisation (#2322)
sparrowDom Dec 5, 2024
040ad7a
add missing natspec (#2321)
sparrowDom Dec 5, 2024
d9c6def
L-02 Missing Docstrings (#2319)
sparrowDom Dec 5, 2024
5e57112
Correct globals storage
DanielVF Dec 10, 2024
eb957f1
Only empty accounts can rebaseOptIn if already rebasing
DanielVF Dec 17, 2024
d285b32
gas optimisation
sparrowDom Dec 17, 2024
55efcd6
remove extra new line
sparrowDom Dec 17, 2024
412e93d
Merge remote-tracking branch 'origin/master' into sparrowDom/rebaseEl…
sparrowDom Dec 17, 2024
c980607
optimise gas when setting alternativeCreditsPerToken (#2325)
sparrowDom Dec 17, 2024
07b3e70
Certora Formal Verification for OUSD (#2329)
Roy-Certora Dec 19, 2024
18d4a51
Fix certora spec. Starting with an invalid state would result in an i…
DanielVF Jan 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 122 additions & 10 deletions contracts/contracts/token/OUSD.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
import { InitializableERC20Detailed } from "../utils/InitializableERC20Detailed.sol";
import { StableMath } from "../utils/StableMath.sol";
import { Governable } from "../governance/Governable.sol";

// TODO DELETE
import "hardhat/console.sol";
/**
* NOTE that this is an ERC20 token but the invariant that the sum of
* balanceOf(x) for all x is not >= totalSupply(). This is a consequence of the
Expand All @@ -32,17 +33,35 @@
);
event AccountRebasingEnabled(address account);
event AccountRebasingDisabled(address account);
event YieldDelegationStart(address fromAccount, address toAccount, uint256 rebasingCreditsPerToken);
event YieldDelegationStop(address fromAccount, address toAccount, uint256 rebasingCreditsPerToken);

enum RebaseOptions {
NotSet,
OptOut,
OptIn
OptIn,
Delegator,
Delegatee
}

uint256 private constant MAX_SUPPLY = ~uint128(0); // (2^128) - 1
DanielVF marked this conversation as resolved.
Show resolved Hide resolved
uint256 public _totalSupply;
DanielVF marked this conversation as resolved.
Show resolved Hide resolved
mapping(address => mapping(address => uint256)) private _allowances;
address public vaultAddress = address(0);
DanielVF marked this conversation as resolved.
Show resolved Hide resolved
/**
* This used to represent rebasing credits where balance would be derived using
* global contract `rebasingCreditsPerToken`. Or non rebasing where balance is derived
* using the account specific `nonRebasingCreditsPerToken`.
*
* While the above functionality still stands the _creditBalances alone only partly represents
* the third type of accounts which are part of yield delegation (delegators and delegatees).
* In those cases the _creditBalances express the amount of balance that counts towards the
* yield of a specific account. The `_amountAdjustement` supplements the balances logic and
* subtracts token balances from yield delegatees and adds to yield delegators to make up the
* exact token balances. The `_amountAdjustement` retain their value between rebases.
* TODO (might need more in dept explanation of _amountAdjustement)
*
*/
mapping(address => uint256) private _creditBalances;
uint256 private _rebasingCredits;
uint256 private _rebasingCreditsPerToken;
Expand All @@ -52,6 +71,18 @@
mapping(address => uint256) public nonRebasingCreditsPerToken;
mapping(address => RebaseOptions) public rebaseState;
mapping(address => uint256) public isUpgraded;
DanielVF marked this conversation as resolved.
Show resolved Hide resolved
/**
* Facilitates the correct token balances in accounts affected by yield delegation
*/
mapping(address => uint256) private _amountAdjustement;
/**
* @dev accounts delegating yield to another account
*/
mapping(address => address) public yieldDelegateFrom;
/**
* @dev accounts having yield delegated from another account
*/
mapping(address => address) public yieldDelegateTo;

uint256 private constant RESOLUTION_INCREASE = 1e9;

Expand Down Expand Up @@ -121,9 +152,27 @@
override
returns (uint256)
{
if (_creditBalances[_account] == 0) return 0;
return
_creditBalances[_account].divPrecisely(_creditsPerToken(_account));
uint256 nonDelegatedBalance;
if (_creditBalances[_account] == 0) nonDelegatedBalance = 0;
else {
nonDelegatedBalance = _creditBalances[_account].divPrecisely(_creditsPerToken(_account));
}

return _adjustBalanceForYieldDelegation(_account, nonDelegatedBalance);
}

function _adjustBalanceForYieldDelegation(address _account, uint256 _nonDelegatedBalance)
internal
view
returns(uint256)
{

if (rebaseState[_account] == RebaseOptions.Delegator) {
return _nonDelegatedBalance + _amountAdjustement[_account];
} else if (rebaseState[_account] == RebaseOptions.Delegatee) {
return _nonDelegatedBalance - _amountAdjustement[_account];
}
return _nonDelegatedBalance;
}

/**
Expand All @@ -138,6 +187,7 @@
view
returns (uint256, uint256)
{
// TODO add yield delegation
uint256 cpt = _creditsPerToken(_account);
if (cpt == 1e27) {
// For a period before the resolution upgrade, we created all new
Expand Down Expand Up @@ -167,6 +217,7 @@
bool
)
{
// TODO add yield delegation
return (
_creditBalances[_account],
_creditsPerToken(_account),
Expand Down Expand Up @@ -242,11 +293,38 @@
uint256 creditsCredited = _value.mulTruncate(_creditsPerToken(_to));
uint256 creditsDeducted = _value.mulTruncate(_creditsPerToken(_from));

_creditBalances[_from] = _creditBalances[_from].sub(
creditsDeducted,
"Transfer amount exceeds balance"
);
_creditBalances[_to] = _creditBalances[_to].add(creditsCredited);
if (rebaseState[_from] == RebaseOptions.Delegator) {
address delegatee = yieldDelegateFrom[_from];
// adjust delegator and delegatee fixed amounts
_amountAdjustement[_from] -= _value;
DanielVF marked this conversation as resolved.
Show resolved Hide resolved
_amountAdjustement[delegatee] -= _value;
// also adjust the reduced yield of the delegatee
_creditBalances[delegatee] = _creditBalances[delegatee].sub(
creditsDeducted,
//"Transfer amount exceeds balance"
"Transfer amount exceeds balance delegatee"
);
} else {
// can not deduct _creditBalances that have been delegated since
// balance checks in transfer & transferFrom prevent it
_creditBalances[_from] = _creditBalances[_from].sub(
creditsDeducted,
"Transfer amount exceeds balance"
);

}

if (rebaseState[_to] == RebaseOptions.Delegator) {
address delegator = yieldDelegateTo[_to];

Check warning on line 318 in contracts/contracts/token/OUSD.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/token/OUSD.sol#L318

Added line #L318 was not covered by tests
// adjust delegator and delegatee fixed amounts
_amountAdjustement[_to] += _value;
_amountAdjustement[delegator] += _value;

Check warning on line 321 in contracts/contracts/token/OUSD.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/token/OUSD.sol#L320-L321

Added lines #L320 - L321 were not covered by tests
// also adjust the additional yield of the delegator
_creditBalances[delegator] = _creditBalances[delegator]

Check warning on line 323 in contracts/contracts/token/OUSD.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/token/OUSD.sol#L323

Added line #L323 was not covered by tests
.add(creditsCredited);
} else {
_creditBalances[_to] = _creditBalances[_to].add(creditsCredited);
}

if (isNonRebasingTo && !isNonRebasingFrom) {
// Transfer to non-rebasing account from rebasing account, credits
Expand Down Expand Up @@ -460,6 +538,7 @@
function _isNonRebasingAccount(address _account) internal returns (bool) {
bool isContract = Address.isContract(_account);
if (isContract && rebaseState[_account] == RebaseOptions.NotSet) {
DanielVF marked this conversation as resolved.
Show resolved Hide resolved
// TODO: make sure this never executes with yield delegators or delegatees
_ensureRebasingMigration(_account);
}
return nonRebasingCreditsPerToken[_account] > 0;
Expand Down Expand Up @@ -559,6 +638,39 @@
emit AccountRebasingDisabled(msg.sender);
}

function governanceDelegateYield(address _accountSource, address _accountReceiver)
public
onlyGovernor
{
if (rebaseState[_accountSource] == RebaseOptions.OptOut) {
_rebaseOptIn(_accountSource);

Check warning on line 646 in contracts/contracts/token/OUSD.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/token/OUSD.sol#L646

Added line #L646 was not covered by tests
}
if (rebaseState[_accountReceiver] == RebaseOptions.OptOut) {
_rebaseOptIn(_accountSource);

Check warning on line 649 in contracts/contracts/token/OUSD.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/token/OUSD.sol#L649

Added line #L649 was not covered by tests
}
uint256 accountSourceBalance = balanceOf(_accountSource);

rebaseState[_accountSource] = RebaseOptions.Delegator;
rebaseState[_accountReceiver] = RebaseOptions.Delegatee;

// delegate all yield and adjust for balances
_creditBalances[_accountReceiver] += _creditBalances[_accountSource];
_creditBalances[_accountSource] = 0;
_amountAdjustement[_accountSource] = accountSourceBalance;
_amountAdjustement[_accountReceiver] = accountSourceBalance;
yieldDelegateFrom[_accountSource] = _accountReceiver;
yieldDelegateTo[_accountReceiver] = _accountSource;

emit YieldDelegationStart(_accountSource, _accountReceiver, _rebasingCreditsPerToken);
}

function governanceStopYieldDelegation(address _accountSource)

Check warning on line 667 in contracts/contracts/token/OUSD.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/token/OUSD.sol#L667

Added line #L667 was not covered by tests
public
onlyGovernor
{
require(false, "Needs implementation");
}

/**
* @dev Modify the supply without minting new tokens. This uses a change in
* the exchange rate between "credits" and OUSD tokens to change balances.
Expand Down
73 changes: 73 additions & 0 deletions contracts/test/token/ousd.js
Original file line number Diff line number Diff line change
Expand Up @@ -849,4 +849,77 @@
await checkTransferOut(5);
await checkTransferOut(9);
});

describe.only("Delegating yield", function () {

Check failure on line 853 in contracts/test/token/ousd.js

View workflow job for this annotation

GitHub Actions / Contracts Linter

describe.only not permitted
it("Should delegate rebase to another account", async () => {
let { ousd, vault, matt, josh, anna, usdc, governor } = fixture;

await ousd.connect(matt).transfer(anna.address, ousdUnits("10"));
await ousd.connect(matt).transfer(josh.address, ousdUnits("10"));

await expect(josh).has.an.approxBalanceOf("110.00", ousd);
await expect(matt).has.an.approxBalanceOf("80.00", ousd);
await expect(anna).has.an.approxBalanceOf("10", ousd);

await ousd
.connect(governor)
// matt delegates yield to anna
.governanceDelegateYield(matt.address, anna.address);

// Transfer USDC into the Vault to simulate yield
await usdc.connect(matt).transfer(vault.address, usdcUnits("200"));
await vault.rebase();

await expect(josh).has.an.approxBalanceOf("220.00", ousd);
await expect(matt).has.an.approxBalanceOf("80.00", ousd);
// 10 of own rebase + 80 from matt + 10 existing balance
await expect(anna).has.an.balanceOf("100", ousd);

await ousd.connect(anna).transfer(josh.address, ousdUnits("10"));

await expect(josh).has.an.approxBalanceOf("230.00", ousd);
await expect(matt).has.an.approxBalanceOf("80.00", ousd);
await expect(anna).has.an.balanceOf("90", ousd);

console.log("Matt transfering to josh");
await ousd.connect(matt).transfer(josh.address, ousdUnits("80"));
console.log("Anna transfering to josh");
await ousd.connect(anna).transfer(josh.address, ousdUnits("90"))

await expect(josh).has.an.approxBalanceOf("400", ousd);
await expect(matt).has.an.approxBalanceOf("0", ousd);
await expect(anna).has.an.balanceOf("0", ousd);
});

it("Should delegate rebase to another account initially having 0 balance", async () => {
let { ousd, vault, matt, josh, anna, usdc, governor } = fixture;

await expect(josh).has.an.approxBalanceOf("100.00", ousd);
await expect(matt).has.an.approxBalanceOf("100.00", ousd);
await expect(anna).has.an.balanceOf("0", ousd);

await ousd
.connect(governor)
// matt delegates yield to anna
.governanceDelegateYield(matt.address, anna.address);

// Transfer USDC into the Vault to simulate yield
await usdc.connect(matt).transfer(vault.address, usdcUnits("200"));
await vault.rebase();

await expect(josh).has.an.approxBalanceOf("200.00", ousd);
await expect(matt).has.an.approxBalanceOf("100.00", ousd);
await expect(anna).has.an.balanceOf("100", ousd);

await ousd.connect(anna).transfer(josh.address, ousdUnits("10"));

await expect(josh).has.an.approxBalanceOf("210.00", ousd);
await expect(matt).has.an.approxBalanceOf("100.00", ousd);
await expect(anna).has.an.balanceOf("90", ousd);
});

it("should be able to chenge yield delegation", async () => {
//A Should delegate to account B, rebase with profit, delegate to account C and all have correct balances
});
});
});
Loading