From 2b3189f39c89ab01e34e2f0a8e0a1fa6bf079c96 Mon Sep 17 00:00:00 2001 From: Domen Grabec Date: Fri, 15 Nov 2024 15:15:50 +0100 Subject: [PATCH] Transfer unit tests for new token implementation (#2310) * remove nonreentrant modifiers from the toke code as they are not needed * add setup for all account types * check all relevant contract initial states * add test between any possible contract accounts * prettier * add some documentation and prettier --- .../contracts/mocks/TestUpgradedOUSD.sol | 25 ++ contracts/contracts/token/OUSD.sol | 4 +- contracts/deploy/deployActions.js | 8 +- contracts/test/_fixture.js | 383 ++++++++++++++++-- contracts/test/token/ousd.js | 4 +- contracts/test/token/token-transfers.js | 230 +++++++++++ 6 files changed, 608 insertions(+), 46 deletions(-) create mode 100644 contracts/contracts/mocks/TestUpgradedOUSD.sol create mode 100644 contracts/test/token/token-transfers.js diff --git a/contracts/contracts/mocks/TestUpgradedOUSD.sol b/contracts/contracts/mocks/TestUpgradedOUSD.sol new file mode 100644 index 0000000000..9d3555f9a2 --- /dev/null +++ b/contracts/contracts/mocks/TestUpgradedOUSD.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "../token/OUSD.sol"; + +// used to alter internal state of OUSD contract +contract TestUpgradedOUSD is OUSD { + constructor() OUSD() {} + + function overwriteCreditBalances(address _account, uint256 _creditBalance) + public + { + _creditBalances[_account] = _creditBalance; + } + + function overwriteAlternativeCPT(address _account, uint256 _acpt) public { + alternativeCreditsPerToken[_account] = _acpt; + } + + function overwriteRebaseState(address _account, RebaseOptions _rebaseOption) + public + { + rebaseState[_account] = _rebaseOption; + } +} diff --git a/contracts/contracts/token/OUSD.sol b/contracts/contracts/token/OUSD.sol index 96780e4aa4..dead3a4f93 100644 --- a/contracts/contracts/token/OUSD.sol +++ b/contracts/contracts/token/OUSD.sol @@ -48,7 +48,7 @@ contract OUSD is Governable { uint256 public _totalSupply; mapping(address => mapping(address => uint256)) private _allowances; address public vaultAddress = address(0); - mapping(address => uint256) private _creditBalances; + mapping(address => uint256) internal _creditBalances; uint256 private _rebasingCredits; // Sum of all rebasing credits (_creditBalances for rebasing accounts) uint256 private _rebasingCreditsPerToken; uint256 public nonRebasingSupply; // All nonrebasing balances @@ -538,7 +538,7 @@ contract OUSD is Governable { // prettier-ignore require( state == RebaseOptions.StdNonRebasing || - state == RebaseOptions.NotSet, + state == RebaseOptions.NotSet, "Only standard non-rebasing accounts can opt in" ); diff --git a/contracts/deploy/deployActions.js b/contracts/deploy/deployActions.js index 2c6d7d5a1e..29a136b8a6 100644 --- a/contracts/deploy/deployActions.js +++ b/contracts/deploy/deployActions.js @@ -6,6 +6,7 @@ const { getOracleAddresses, isMainnet, isHolesky, + isTest, } = require("../test/helpers.js"); const { deployWithConfirmation, withConfirmation } = require("../utils/deploy"); const { @@ -1190,7 +1191,12 @@ const deployOUSDCore = async () => { await deployWithConfirmation("VaultProxy"); // Main contracts - const dOUSD = await deployWithConfirmation("OUSD"); + let dOUSD; + if (isTest) { + dOUSD = await deployWithConfirmation("TestUpgradedOUSD"); + } else { + dOUSD = await deployWithConfirmation("OUSD"); + } const dVault = await deployWithConfirmation("Vault"); const dVaultCore = await deployWithConfirmation("VaultCore"); const dVaultAdmin = await deployWithConfirmation("VaultAdmin"); diff --git a/contracts/test/_fixture.js b/contracts/test/_fixture.js index 908eea9f95..3f06329f9c 100644 --- a/contracts/test/_fixture.js +++ b/contracts/test/_fixture.js @@ -20,6 +20,7 @@ const { fundAccounts, fundAccountsForOETHUnitTests, } = require("../utils/funding"); + const { replaceContractAt } = require("../utils/hardhat"); const { getAssetAddresses, @@ -189,11 +190,297 @@ const simpleOETHFixture = deployments.createFixture(async () => { }; }); -const defaultFixture = deployments.createFixture(async () => { - if (!snapshotId && !isFork) { - snapshotId = await nodeSnapshot(); +const getVaultAndTokenConracts = async () => { + const ousdProxy = await ethers.getContract("OUSDProxy"); + const vaultProxy = await ethers.getContract("VaultProxy"); + + const ousd = await ethers.getContractAt("OUSD", ousdProxy.address); + // the same contract as the "ousd" one just with some unlocked features + const ousdUnlocked = await ethers.getContractAt( + "TestUpgradedOUSD", + ousdProxy.address + ); + + const vault = await ethers.getContractAt("IVault", vaultProxy.address); + + const oethProxy = await ethers.getContract("OETHProxy"); + const OETHVaultProxy = await ethers.getContract("OETHVaultProxy"); + const oethVault = await ethers.getContractAt( + "IVault", + OETHVaultProxy.address + ); + const oeth = await ethers.getContractAt("OETH", oethProxy.address); + + let woeth, woethProxy, mockNonRebasing, mockNonRebasingTwo; + + if (isFork) { + woethProxy = await ethers.getContract("WOETHProxy"); + woeth = await ethers.getContractAt("WOETH", woethProxy.address); + } else { + // Mock contracts for testing rebase opt out + mockNonRebasing = await ethers.getContract("MockNonRebasing"); + await mockNonRebasing.setOUSD(ousd.address); + mockNonRebasingTwo = await ethers.getContract("MockNonRebasingTwo"); + await mockNonRebasingTwo.setOUSD(ousd.address); } + return { + ousd, + ousdUnlocked, + vault, + oethVault, + oeth, + woeth, + mockNonRebasing, + mockNonRebasingTwo, + }; +}; + +/** + * This fixture creates the 4 different OUSD contract account types in all of + * the possible storage configuration: StdRebasing, StdNonRebasing, YieldDelegationSource, + * YieldDelegationTarget + */ +const createAccountTypes = async ({ vault, ousd, ousdUnlocked, deploy }) => { + const signers = await hre.ethers.getSigners(); + const matt = signers[4]; + const governor = signers[1]; + + if (!isFork) { + await fundAccounts(); + const dai = await ethers.getContract("MockDAI"); + await dai.connect(matt).approve(vault.address, daiUnits("1000")); + await vault.connect(matt).mint(dai.address, daiUnits("1000"), 0); + } + + // yiedlsource + // yieldtarget + + const createAccount = async () => { + let account = ethers.Wallet.createRandom(); + // Give ETH to user + await hardhatSetBalance(account.address, "1000000"); + account = account.connect(ethers.provider); + return account; + }; + + const createContract = async (name) => { + const fullName = `MockNonRebasing_${name}`; + await deploy(fullName, { + from: matt.address, + contract: "MockNonRebasing", + }); + + const contract = await ethers.getContract(fullName); + await contract.setOUSD(ousd.address); + + return contract; + }; + + // generate alternativeCreditsPerToken BigNumber and creditBalance BigNumber + // for a given credits per token + const generateCreditsBalancePair = ({ creditsPerToken, tokenBalance }) => { + const creditsPerTokenBN = parseUnits(`${creditsPerToken}`, 27); + // 1e18 * 1e27 / 1e18 + const creditsBalanceBN = tokenBalance + .mul(creditsPerTokenBN) + .div(parseUnits("1", 18)); + + return { + creditsPerTokenBN, + creditsBalanceBN, + }; + }; + + const createNonRebasingNotSetAlternativeCptContract = async ({ + name, + creditsPerToken, + balance, + }) => { + const nonrebase_cotract_notSet_altcpt_gt = await createContract(name); + await ousd + .connect(matt) + .transfer(nonrebase_cotract_notSet_altcpt_gt.address, balance); + const { creditsPerTokenBN, creditsBalanceBN } = generateCreditsBalancePair({ + creditsPerToken, + tokenBalance: balance, + }); + await ousdUnlocked + .connect(matt) + .overwriteCreditBalances( + nonrebase_cotract_notSet_altcpt_gt.address, + creditsBalanceBN + ); + await ousdUnlocked + .connect(matt) + .overwriteAlternativeCPT( + nonrebase_cotract_notSet_altcpt_gt.address, + creditsPerTokenBN + ); + await ousdUnlocked.connect(matt).overwriteRebaseState( + nonrebase_cotract_notSet_altcpt_gt.address, + 0 // NotSet + ); + + return nonrebase_cotract_notSet_altcpt_gt; + }; + + const rebase_eoa_notset_0 = await createAccount(); + await ousd + .connect(matt) + .transfer(rebase_eoa_notset_0.address, ousdUnits("11")); + const rebase_eoa_notset_1 = await createAccount(); + await ousd + .connect(matt) + .transfer(rebase_eoa_notset_1.address, ousdUnits("12")); + + const rebase_eoa_stdRebasing_0 = await createAccount(); + await ousd + .connect(matt) + .transfer(rebase_eoa_stdRebasing_0.address, ousdUnits("21")); + await ousd.connect(rebase_eoa_stdRebasing_0).rebaseOptOut(); + await ousd.connect(rebase_eoa_stdRebasing_0).rebaseOptIn(); + const rebase_eoa_stdRebasing_1 = await createAccount(); + await ousd + .connect(matt) + .transfer(rebase_eoa_stdRebasing_1.address, ousdUnits("22")); + await ousd.connect(rebase_eoa_stdRebasing_1).rebaseOptOut(); + await ousd.connect(rebase_eoa_stdRebasing_1).rebaseOptIn(); + + const rebase_contract_0 = await createContract("rebase_contract_0"); + await ousd.connect(matt).transfer(rebase_contract_0.address, ousdUnits("33")); + await rebase_contract_0.connect(matt).rebaseOptIn(); + const rebase_contract_1 = await createContract("rebase_contract_1"); + await ousd.connect(matt).transfer(rebase_contract_1.address, ousdUnits("34")); + await rebase_contract_1.connect(matt).rebaseOptIn(); + + const nonrebase_eoa_0 = await createAccount(); + await ousd.connect(matt).transfer(nonrebase_eoa_0.address, ousdUnits("44")); + await ousd.connect(nonrebase_eoa_0).rebaseOptOut(); + const nonrebase_eoa_1 = await createAccount(); + await ousd.connect(matt).transfer(nonrebase_eoa_1.address, ousdUnits("45")); + await ousd.connect(nonrebase_eoa_1).rebaseOptOut(); + + const nonrebase_cotract_0 = await createContract("nonrebase_cotract_0"); + await ousd + .connect(matt) + .transfer(nonrebase_cotract_0.address, ousdUnits("55")); + await nonrebase_cotract_0.connect(matt).rebaseOptIn(); + await nonrebase_cotract_0.connect(matt).rebaseOptOut(); + const nonrebase_cotract_1 = await createContract("nonrebase_cotract_1"); + await ousd + .connect(matt) + .transfer(nonrebase_cotract_1.address, ousdUnits("56")); + await nonrebase_cotract_1.connect(matt).rebaseOptIn(); + await nonrebase_cotract_1.connect(matt).rebaseOptOut(); + + const nonrebase_cotract_notSet_0 = await createContract( + "nonrebase_cotract_notSet_0" + ); + const nonrebase_cotract_notSet_1 = await createContract( + "nonrebase_cotract_notSet_1" + ); + + const nonrebase_cotract_notSet_altcpt_gt_0 = + await createNonRebasingNotSetAlternativeCptContract({ + name: "nonrebase_cotract_notSet_altcpt_gt_0", + creditsPerToken: 0.934232, + balance: ousdUnits("65"), + }); + + const nonrebase_cotract_notSet_altcpt_gt_1 = + await createNonRebasingNotSetAlternativeCptContract({ + name: "nonrebase_cotract_notSet_altcpt_gt_1", + creditsPerToken: 0.890232, + balance: ousdUnits("66"), + }); + + const rebase_delegate_source_0 = await createAccount(); + await ousd + .connect(matt) + .transfer(rebase_delegate_source_0.address, ousdUnits("76")); + const rebase_delegate_target_0 = await createAccount(); + await ousd + .connect(matt) + .transfer(rebase_delegate_target_0.address, ousdUnits("77")); + + await ousd + .connect(governor) + .delegateYield( + rebase_delegate_source_0.address, + rebase_delegate_target_0.address + ); + + const rebase_delegate_source_1 = await createAccount(); + await ousd + .connect(matt) + .transfer(rebase_delegate_source_1.address, ousdUnits("87")); + const rebase_delegate_target_1 = await createAccount(); + await ousd + .connect(matt) + .transfer(rebase_delegate_target_1.address, ousdUnits("88")); + + await ousd + .connect(governor) + .delegateYield( + rebase_delegate_source_1.address, + rebase_delegate_target_1.address + ); + + // matt burn remaining OUSD + await vault.connect(matt).redeemAll(ousdUnits("0")); + + return { + // StdRebasing account type: + // - all have alternativeCreditsPerToken = 0 + // - _creditBalances non zero using global contract's rebasingCredits to compute balance + + // EOA account that has rebaseState: NotSet + rebase_eoa_notset_0, + rebase_eoa_notset_1, + // EOA account that has rebaseState: StdRebasing + rebase_eoa_stdRebasing_0, + rebase_eoa_stdRebasing_1, + // contract account that has rebaseState: StdRebasing + rebase_contract_0, + rebase_contract_1, + + // StdNonRebasing account type: + // - alternativeCreditsPerToken > 0 & 1e18 for new accounts + // - _creditBalances non zero: + // - new accounts match _creditBalances to their token balance + // - older accounts use _creditBalances & alternativeCreditsPerToken to compute token balance + + // EOA account that has rebaseState: StdNonRebasing + nonrebase_eoa_0, + nonrebase_eoa_1, + // contract account that has rebaseState: StdNonRebasing + nonrebase_cotract_0, + nonrebase_cotract_1, + // contract account that has rebaseState: NotSet + nonrebase_cotract_notSet_0, + nonrebase_cotract_notSet_1, + // contract account that has rebaseState: NotSet & alternativeCreditsPerToken > 0 + // note: these are older accounts that have been migrated by the older versions of + // of the code without explicitly setting rebaseState to StdNonRebasing + nonrebase_cotract_notSet_altcpt_gt_0, + nonrebase_cotract_notSet_altcpt_gt_1, + + // account delegating yield + rebase_delegate_source_0, + rebase_delegate_source_1, + + // account receiving delegated yield + rebase_delegate_target_0, + rebase_delegate_target_1, + }; +}; + +/** + * Vault and token fixture with extra functionality regarding different types of accounts + * (rebaseStates and alternativeCreditsPerToken ) when testing token contract behaviour + */ +const loadTokenTransferFixture = deployments.createFixture(async () => { log(`Forked from block: ${await hre.ethers.provider.getBlockNumber()}`); log(`Before deployments with param "${isFork ? undefined : ["unit_tests"]}"`); @@ -209,30 +496,46 @@ const defaultFixture = deployments.createFixture(async () => { const { governorAddr, strategistAddr, timelockAddr } = await getNamedAccounts(); - const ousdProxy = await ethers.getContract("OUSDProxy"); - const vaultProxy = await ethers.getContract("VaultProxy"); + const vaultAndTokenConracts = await getVaultAndTokenConracts(); - const compoundStrategyProxy = await ethers.getContract( - "CompoundStrategyProxy" - ); + const accountTypes = await createAccountTypes({ + ousd: vaultAndTokenConracts.ousd, + ousdUnlocked: vaultAndTokenConracts.ousdUnlocked, + vault: vaultAndTokenConracts.vault, + deploy: deployments.deploy, + governor: deployments.governor, + }); - const ousd = await ethers.getContractAt("OUSD", ousdProxy.address); - const vault = await ethers.getContractAt("IVault", vaultProxy.address); + return { + ...vaultAndTokenConracts, + ...accountTypes, + governorAddr, + strategistAddr, + timelockAddr, + }; +}); - const oethProxy = await ethers.getContract("OETHProxy"); - const OETHVaultProxy = await ethers.getContract("OETHVaultProxy"); - const oethVault = await ethers.getContractAt( - "IVault", - OETHVaultProxy.address - ); - const oeth = await ethers.getContractAt("OETH", oethProxy.address); +const defaultFixture = deployments.createFixture(async () => { + if (!snapshotId && !isFork) { + snapshotId = await nodeSnapshot(); + } - let woeth, woethProxy; + log(`Forked from block: ${await hre.ethers.provider.getBlockNumber()}`); - if (isFork) { - woethProxy = await ethers.getContract("WOETHProxy"); - woeth = await ethers.getContractAt("WOETH", woethProxy.address); - } + log(`Before deployments with param "${isFork ? undefined : ["unit_tests"]}"`); + + // Run the contract deployments + await deployments.fixture(isFork ? undefined : ["unit_tests"], { + keepExistingDeployments: true, + fallbackToGlobal: true, + }); + + log(`Block after deployments: ${await hre.ethers.provider.getBlockNumber()}`); + + const { governorAddr, strategistAddr, timelockAddr } = + await getNamedAccounts(); + + const vaultAndTokenConracts = await getVaultAndTokenConracts(); const harvesterProxy = await ethers.getContract("HarvesterProxy"); const harvester = await ethers.getContractAt( @@ -253,6 +556,11 @@ const defaultFixture = deployments.createFixture(async () => { const CompoundStrategyFactory = await ethers.getContractFactory( "CompoundStrategy" ); + + const compoundStrategyProxy = await ethers.getContract( + "CompoundStrategyProxy" + ); + const compoundStrategy = await ethers.getContractAt( "CompoundStrategy", compoundStrategyProxy.address @@ -341,8 +649,6 @@ const defaultFixture = deployments.createFixture(async () => { sfrxETH, sDAI, usdcMetaMorphoSteakHouseVault, - mockNonRebasing, - mockNonRebasingTwo, LUSD, fdai, fusdt, @@ -613,12 +919,6 @@ const defaultFixture = deployments.createFixture(async () => { "MockChainlinkOracleFeedETH" ); - // Mock contracts for testing rebase opt out - mockNonRebasing = await ethers.getContract("MockNonRebasing"); - await mockNonRebasing.setOUSD(ousd.address); - mockNonRebasingTwo = await ethers.getContract("MockNonRebasingTwo"); - await mockNonRebasingTwo.setOUSD(ousd.address); - flipper = await ethers.getContract("Flipper"); const LUSDMetaStrategyProxy = await ethers.getContract( @@ -649,10 +949,12 @@ const defaultFixture = deployments.createFixture(async () => { const sGovernor = await ethers.provider.getSigner(governorAddr); // Add TUSD in fixture, it is disabled by default in deployment - await vault.connect(sGovernor).supportAsset(assetAddresses.TUSD, 0); + await vaultAndTokenConracts.vault + .connect(sGovernor) + .supportAsset(assetAddresses.TUSD, 0); // Enable capital movement - await vault.connect(sGovernor).unpauseCapital(); + await vaultAndTokenConracts.vault.connect(sGovernor).unpauseCapital(); } const signers = await hre.ethers.getSigners(); @@ -678,11 +980,16 @@ const defaultFixture = deployments.createFixture(async () => { // Matt and Josh each have $100 OUSD for (const user of [matt, josh]) { - await dai.connect(user).approve(vault.address, daiUnits("100")); - await vault.connect(user).mint(dai.address, daiUnits("100"), 0); + await dai + .connect(user) + .approve(vaultAndTokenConracts.vault.address, daiUnits("100")); + await vaultAndTokenConracts.vault + .connect(user) + .mint(dai.address, daiUnits("100"), 0); } } return { + ...vaultAndTokenConracts, // Accounts matt, josh, @@ -696,13 +1003,9 @@ const defaultFixture = deployments.createFixture(async () => { timelock, oldTimelock, // Contracts - ousd, - vault, vaultValueChecker, harvester, dripper, - mockNonRebasing, - mockNonRebasingTwo, // Oracle chainlinkOracleFeedDAI, chainlinkOracleFeedUSDT, @@ -779,9 +1082,7 @@ const defaultFixture = deployments.createFixture(async () => { fusdt, // OETH - oethVault, oethVaultValueChecker, - oeth, frxETH, sfrxETH, sDAI, @@ -792,7 +1093,6 @@ const defaultFixture = deployments.createFixture(async () => { lidoWithdrawalStrategy, balancerREthStrategy, oethMorphoAaveStrategy, - woeth, convexEthMetaStrategy, oethDripper, oethHarvester, @@ -2540,6 +2840,7 @@ module.exports = { resetAllowance, defaultFixture, oethDefaultFixture, + loadTokenTransferFixture, mockVaultFixture, compoundFixture, compoundVaultFixture, diff --git a/contracts/test/token/ousd.js b/contracts/test/token/ousd.js index 0c9967bb47..996af5f481 100644 --- a/contracts/test/token/ousd.js +++ b/contracts/test/token/ousd.js @@ -786,7 +786,7 @@ describe("Token", function () { const beforeReceiver = await ousd.balanceOf(mockNonRebasing.address); await ousd.connect(matt).transfer(mockNonRebasing.address, amount); const afterReceiver = await ousd.balanceOf(mockNonRebasing.address); - expect(beforeReceiver.add(amount)).to.equal(afterReceiver); + await expect(beforeReceiver.add(amount)).to.equal(afterReceiver); }; // Helper to verify balance-exact transfers out @@ -794,7 +794,7 @@ describe("Token", function () { const beforeReceiver = await ousd.balanceOf(mockNonRebasing.address); await mockNonRebasing.transfer(matt.address, amount); const afterReceiver = await ousd.balanceOf(mockNonRebasing.address); - expect(beforeReceiver.sub(amount)).to.equal(afterReceiver); + await expect(beforeReceiver.sub(amount)).to.equal(afterReceiver); }; // In diff --git a/contracts/test/token/token-transfers.js b/contracts/test/token/token-transfers.js new file mode 100644 index 0000000000..2c1dd9bb9f --- /dev/null +++ b/contracts/test/token/token-transfers.js @@ -0,0 +1,230 @@ +const { expect } = require("chai"); +const { loadTokenTransferFixture } = require("../_fixture"); + +const { isFork, ousdUnits } = require("../helpers"); + +describe("Token Transfers", function () { + if (isFork) { + this.timeout(0); + } + let fixture; + beforeEach(async () => { + fixture = await loadTokenTransferFixture(); + }); + + it("Accounts and ousd contract should have correct initial states", async () => { + const { + rebase_eoa_notset_0, + rebase_eoa_notset_1, + rebase_eoa_stdRebasing_0, + rebase_eoa_stdRebasing_1, + rebase_contract_0, + rebase_contract_1, + nonrebase_eoa_0, + nonrebase_eoa_1, + nonrebase_cotract_0, + nonrebase_cotract_1, + nonrebase_cotract_notSet_0, + nonrebase_cotract_notSet_1, + nonrebase_cotract_notSet_altcpt_gt_0, + nonrebase_cotract_notSet_altcpt_gt_1, + rebase_delegate_source_0, + rebase_delegate_source_1, + rebase_delegate_target_0, + rebase_delegate_target_1, + ousd, + } = fixture; + + expect(await ousd.rebaseState(rebase_eoa_notset_0.address)).to.equal(0); // rebaseState:NotSet + await expect(rebase_eoa_notset_0).has.a.balanceOf("11", ousd); + expect(await ousd.rebaseState(rebase_eoa_notset_1.address)).to.equal(0); // rebaseState:NotSet + await expect(rebase_eoa_notset_1).has.a.balanceOf("12", ousd); + + expect(await ousd.rebaseState(rebase_eoa_stdRebasing_0.address)).to.equal( + 2 + ); // rebaseState:StdRebasing + await expect(rebase_eoa_stdRebasing_0).has.a.balanceOf("21", ousd); + expect(await ousd.rebaseState(rebase_eoa_stdRebasing_1.address)).to.equal( + 2 + ); // rebaseState:StdRebasing + await expect(rebase_eoa_stdRebasing_1).has.a.balanceOf("22", ousd); + + expect(await ousd.rebaseState(rebase_contract_0.address)).to.equal(2); // rebaseState:StdRebasing + await expect(rebase_contract_0).has.a.balanceOf("33", ousd); + expect(await ousd.rebaseState(rebase_contract_1.address)).to.equal(2); // rebaseState:StdRebasing + await expect(rebase_contract_1).has.a.balanceOf("34", ousd); + + expect(await ousd.rebaseState(nonrebase_eoa_0.address)).to.equal(1); // rebaseState:StdNonRebasing + await expect(nonrebase_eoa_0).has.a.balanceOf("44", ousd); + expect(await ousd.rebaseState(nonrebase_eoa_1.address)).to.equal(1); // rebaseState:StdNonRebasing + await expect(nonrebase_eoa_1).has.a.balanceOf("45", ousd); + + expect(await ousd.rebaseState(nonrebase_cotract_0.address)).to.equal(1); // rebaseState:StdNonRebasing + await expect(nonrebase_cotract_0).has.a.balanceOf("55", ousd); + expect(await ousd.rebaseState(nonrebase_cotract_1.address)).to.equal(1); // rebaseState:StdNonRebasing + await expect(nonrebase_cotract_1).has.a.balanceOf("56", ousd); + + expect(await ousd.rebaseState(nonrebase_cotract_notSet_0.address)).to.equal( + 0 + ); // rebaseState:NotSet + await expect(nonrebase_cotract_notSet_0).has.a.balanceOf("0", ousd); + expect(await ousd.rebaseState(nonrebase_cotract_notSet_1.address)).to.equal( + 0 + ); // rebaseState:NotSet + await expect(nonrebase_cotract_notSet_1).has.a.balanceOf("0", ousd); + + expect( + await ousd.rebaseState(nonrebase_cotract_notSet_altcpt_gt_0.address) + ).to.equal(0); // rebaseState:NotSet + await expect(nonrebase_cotract_notSet_altcpt_gt_0).has.a.balanceOf( + "65", + ousd + ); + expect( + await ousd.rebaseState(nonrebase_cotract_notSet_altcpt_gt_1.address) + ).to.equal(0); // rebaseState:NotSet + await expect(nonrebase_cotract_notSet_altcpt_gt_1).has.a.balanceOf( + "66", + ousd + ); + + expect(await ousd.rebaseState(rebase_delegate_source_0.address)).to.equal( + 3 + ); // rebaseState:YieldDelegationSource + await expect(rebase_delegate_source_0).has.a.balanceOf("76", ousd); + expect(await ousd.rebaseState(rebase_delegate_source_1.address)).to.equal( + 3 + ); // rebaseState:YieldDelegationSource + await expect(rebase_delegate_source_1).has.a.balanceOf("87", ousd); + + expect(await ousd.rebaseState(rebase_delegate_target_0.address)).to.equal( + 4 + ); // rebaseState:YieldDelegationTarget + await expect(rebase_delegate_target_0).has.a.balanceOf("77", ousd); + expect(await ousd.rebaseState(rebase_delegate_target_1.address)).to.equal( + 4 + ); // rebaseState:YieldDelegationTarget + await expect(rebase_delegate_target_1).has.a.balanceOf("88", ousd); + + // prettier-ignore + const totalSupply = 11 + 12 + 21 + 22 + 33 + 34 + 44 + + 45 + 55 + 56 + 65 + 66 + 76 + 87 + 77 + 88; + const nonRebasingSupply = 44 + 45 + 55 + 56 + 65 + 66; + expect(await ousd.totalSupply()).to.equal(ousdUnits(`${totalSupply}`)); + expect(await ousd.nonRebasingSupply()).to.equal( + ousdUnits(`${nonRebasingSupply}`) + ); + }); + + const fromAccounts = [ + { + name: "rebase_eoa_notset_0", + affectsRebasingCredits: true, + isContract: false, + }, + { + name: "rebase_eoa_stdRebasing_0", + affectsRebasingCredits: true, + isContract: false, + }, + { + name: "rebase_contract_0", + affectsRebasingCredits: true, + isContract: true, + }, + { + name: "nonrebase_eoa_0", + affectsRebasingCredits: false, + isContract: false, + }, + { + name: "nonrebase_cotract_0", + affectsRebasingCredits: false, + isContract: true, + }, + // can not initiate a transfer from below contract since it has the balance of 0 + //{name: "nonrebase_cotract_notSet_0", affectsRebasingCredits: false, isContract: true}, + { + name: "nonrebase_cotract_notSet_altcpt_gt_0", + affectsRebasingCredits: false, + isContract: true, + }, + { + name: "rebase_delegate_source_0", + affectsRebasingCredits: true, + isContract: false, + }, + { + name: "rebase_delegate_target_0", + affectsRebasingCredits: true, + isContract: false, + }, + ]; + + const toAccounts = [ + { name: "rebase_eoa_notset_1", affectsRebasingCredits: true }, + { name: "rebase_eoa_stdRebasing_1", affectsRebasingCredits: true }, + { name: "rebase_contract_1", affectsRebasingCredits: true }, + { name: "nonrebase_eoa_1", affectsRebasingCredits: false }, + { name: "nonrebase_cotract_1", affectsRebasingCredits: false }, + { name: "nonrebase_cotract_notSet_1", affectsRebasingCredits: false }, + { + name: "nonrebase_cotract_notSet_altcpt_gt_1", + affectsRebasingCredits: false, + }, + { name: "rebase_delegate_source_1", affectsRebasingCredits: true }, + { name: "rebase_delegate_target_1", affectsRebasingCredits: true }, + ]; + + const totalSupply = ousdUnits("792"); + const nonRebasingSupply = ousdUnits("331"); + for (let i = 0; i < fromAccounts.length; i++) { + for (let j = 0; j < toAccounts.length; j++) { + const { + name: fromName, + affectsRebasingCredits: fromAffectsRC, + isContract, + } = fromAccounts[i]; + const { name: toName, affectsRebasingCredits: toAffectsRC } = + toAccounts[j]; + + it(`Should transfer from ${fromName} to ${toName}`, async () => { + const fromAccount = fixture[fromName]; + const toAccount = fixture[toName]; + const { ousd } = fixture; + + const fromBalance = await ousd.balanceOf(fromAccount.address); + const toBalance = await ousd.balanceOf(toAccount.address); + // Random transfer between 2-8 + const amount = ousdUnits(`${2 + Math.random() * 6}`); + + if (isContract) { + await fromAccount.transfer(toAccount.address, amount); + } else { + await ousd.connect(fromAccount).transfer(toAccount.address, amount); + } + + // check balances + await expect(await ousd.balanceOf(fromAccount.address)).to.equal( + fromBalance.sub(amount) + ); + await expect(await ousd.balanceOf(toAccount.address)).to.equal( + toBalance.add(amount) + ); + + let expectedNonRebasingSupply = nonRebasingSupply; + if (!fromAffectsRC) { + expectedNonRebasingSupply = expectedNonRebasingSupply.sub(amount); + } + if (!toAffectsRC) { + expectedNonRebasingSupply = expectedNonRebasingSupply.add(amount); + } + // check global contract (in)variants + await expect(await ousd.totalSupply()).to.equal(totalSupply); + await expect(await ousd.nonRebasingSupply()).to.equal( + expectedNonRebasingSupply + ); + }); + } + } +});