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

enable rebaseOptIn by the governor #1961

Merged
merged 3 commits into from
Dec 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
40 changes: 32 additions & 8 deletions contracts/contracts/token/OUSD.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ contract OUSD is Initializable, InitializableERC20Detailed, Governable {
uint256 rebasingCredits,
uint256 rebasingCreditsPerToken
);
event AccountRebasingEnabled(address account);
event AccountRebasingDisabled(address account);

enum RebaseOptions {
NotSet,
Expand Down Expand Up @@ -469,6 +471,7 @@ contract OUSD is Initializable, InitializableERC20Detailed, Governable {
*/
function _ensureRebasingMigration(address _account) internal {
if (nonRebasingCreditsPerToken[_account] == 0) {
emit AccountRebasingDisabled(_account);
if (_creditBalances[_account] == 0) {
// Since there is no existing balance, we can directly set to
// high resolution, and do not have to do any other bookkeeping
Expand All @@ -488,32 +491,52 @@ contract OUSD is Initializable, InitializableERC20Detailed, Governable {
}
}

/**
* @notice Enable rebasing for an account.
* @dev Add a contract address to the non-rebasing exception list. The
* address's balance will be part of rebases and the account will be exposed
* to upside and downside.
* @param _account Address of the account.
*/
function governanceRebaseOptIn(address _account)
public
nonReentrant
onlyGovernor
{
_rebaseOptIn(_account);
}

/**
* @dev Add a contract address to the non-rebasing exception list. The
* address's balance will be part of rebases and the account will be exposed
* to upside and downside.
*/
function rebaseOptIn() public nonReentrant {
require(_isNonRebasingAccount(msg.sender), "Account has not opted out");
_rebaseOptIn(msg.sender);
}

function _rebaseOptIn(address _account) internal {
require(_isNonRebasingAccount(_account), "Account has not opted out");

// Convert balance into the same amount at the current exchange rate
uint256 newCreditBalance = _creditBalances[msg.sender]
uint256 newCreditBalance = _creditBalances[_account]
.mul(_rebasingCreditsPerToken)
.div(_creditsPerToken(msg.sender));
.div(_creditsPerToken(_account));

// Decreasing non rebasing supply
nonRebasingSupply = nonRebasingSupply.sub(balanceOf(msg.sender));
nonRebasingSupply = nonRebasingSupply.sub(balanceOf(_account));

_creditBalances[msg.sender] = newCreditBalance;
_creditBalances[_account] = newCreditBalance;

// Increase rebasing credits, totalSupply remains unchanged so no
// adjustment necessary
_rebasingCredits = _rebasingCredits.add(_creditBalances[msg.sender]);
_rebasingCredits = _rebasingCredits.add(_creditBalances[_account]);

rebaseState[msg.sender] = RebaseOptions.OptIn;
rebaseState[_account] = RebaseOptions.OptIn;

// Delete any fixed credits per token
delete nonRebasingCreditsPerToken[msg.sender];
delete nonRebasingCreditsPerToken[_account];
emit AccountRebasingEnabled(_account);
}

/**
Expand All @@ -533,6 +556,7 @@ contract OUSD is Initializable, InitializableERC20Detailed, Governable {

// Mark explicitly opted out of rebasing
rebaseState[msg.sender] = RebaseOptions.OptOut;
emit AccountRebasingDisabled(msg.sender);
}

/**
Expand Down
39 changes: 39 additions & 0 deletions contracts/deploy/082_upgrade_oeth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
const { deploymentWithGovernanceProposal } = require("../utils/deploy");

module.exports = deploymentWithGovernanceProposal(
{
deployName: "082_upgrade_oeth",
forceDeploy: false,
reduceQueueTime: true,
deployerIsProposer: true,
},
async ({ ethers, deployWithConfirmation }) => {
const cOETHProxy = await ethers.getContract("OETHProxy");
const eigenLayerStrategyContract =
"0xa4c637e0f704745d182e4d38cab7e7485321d059";

// Deploy new version of OETH contract
const dOETHImpl = await deployWithConfirmation("OETH", []);

const cOETH = await ethers.getContractAt("OETH", cOETHProxy.address);

// Governance Actions
// ----------------
return {
name: "Upgrade the OETH AMO strategy with peg keeping functions.",
actions: [
// Upgrade the OETH token proxy contract to the new implementation
{
contract: cOETHProxy,
signature: "upgradeTo(address)",
args: [dOETHImpl.address],
},
{
contract: cOETH,
signature: "governanceRebaseOptIn(address)",
args: [eigenLayerStrategyContract],
},
],
};
}
);
1 change: 0 additions & 1 deletion contracts/node.sh
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ main()
printf "\n"
echo "🟢 Node initialized"

FORK=true npm run copy-interface-artifacts
FORK=true npx hardhat fund --amount 100000 --network localhost --accountsfromenv true &

# wait for subprocesses to finish
Expand Down
41 changes: 41 additions & 0 deletions contracts/test/token/oeth.fork-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
const { expect } = require("chai");

const { loadDefaultFixture } = require("./../_fixture");
const { isCI } = require("./../helpers");

/**
* Regarding hardcoded addresses:
* The addresses are hardcoded in the test files (instead of
* using them from addresses.js) intentionally. While importing and
* using the variables from that file increases readability, it may
* result in it being a single point of failure. Anyone can update
* the addresses.js file and it may go unnoticed.
*
* Points against this: The on-chain data would still be correct,
* making the tests to fail in case only addresses.js is updated.
*
* Still open to discussion.
*/

describe("ForkTest: OETH", function () {
this.timeout(0);

// Retry up to 3 times on CI
this.retries(isCI ? 3 : 0);

let fixture;
beforeEach(async () => {
fixture = await loadDefaultFixture();
});

describe("verify state", () => {
// These tests use a transaction to call a view function so the gas usage can be reported.
it("Should get total value", async () => {
const { oeth } = fixture;
const eigenLayerStrategyContract =
"0xa4c637e0f704745d182e4d38cab7e7485321d059";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I'd add EigenLayerStrategy to the addresses file

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is almost the same comment I've left on one of @shahthepro's PRs but he made a great point, that when these addresses are immutable, it is safer to leave them hardcoded in the test files, so someone doesn't mistakingly change the address in the addresses.js and that could have unexpected consequences. This comment addresses it.

I think Shah makes a great point, though I don't have strong conviction either way regarding this topic.

// 2 equals OptIn
expect(await oeth.rebaseState(eigenLayerStrategyContract)).to.be.equal(2);
});
});
});
23 changes: 20 additions & 3 deletions contracts/test/token/ousd.js
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,12 @@ describe("Token", function () {

const totalSupplyBefore = await ousd.totalSupply();
await expect(mockNonRebasing).has.an.approxBalanceOf("99.50", ousd);
await mockNonRebasing.rebaseOptIn();

const rebaseTx = await mockNonRebasing.rebaseOptIn();
await expect(rebaseTx)
.to.emit(ousd, "AccountRebasingEnabled")
.withArgs(mockNonRebasing.address);

await expect(mockNonRebasing).has.an.approxBalanceOf("99.50", ousd);
expect(await ousd.totalSupply()).to.equal(totalSupplyBefore);

Expand Down Expand Up @@ -470,7 +475,11 @@ describe("Token", function () {
const initialrebasingCreditsPerTokenHighres =
await ousd.rebasingCreditsPerTokenHighres();

await ousd.connect(matt).rebaseOptOut();
const rebaseTx = await ousd.connect(matt).rebaseOptOut();
await expect(rebaseTx)
.to.emit(ousd, "AccountRebasingDisabled")
.withArgs(matt.address);

// Received 100 from the rebase, the 200 simulated yield was split between
// Matt and Josh
await expect(matt).has.an.approxBalanceOf("200.00", ousd);
Expand Down Expand Up @@ -673,7 +682,15 @@ describe("Token", function () {
vault.address,
daiUnits("100")
);
await mockNonRebasing.mintOusd(vault.address, dai.address, daiUnits("50"));
const tx = await mockNonRebasing.mintOusd(
vault.address,
dai.address,
daiUnits("50")
);
await expect(tx)
.to.emit(ousd, "AccountRebasingDisabled")
.withArgs(mockNonRebasing.address);

await expect(await ousd.totalSupply()).to.equal(
totalSupplyBefore.add(ousdUnits("50"))
);
Expand Down
Loading