which is division before multiplication and hence causes precision loss. The correct code should have been:
I provide the PoC below which shows this loss of reward with example numbers.
const { expect } = require("chai");
const { ethers } = require("hardhat");
const { ETH, getNFTMetadataContract, DEFAULT_ETH_USD_PRICE, DEFAULT_COLLATERAL_RATE, fastForward, DAY, POOL_FEE_PERCENTAGE, fullyUpgradedSmartVaultManager, PROTOCOL_FEE_RATE } = require("./common");
describe('Reward Calculation', async () => {
let LiquidationPoolManager, LiquidationPool, TokenManager,
TST, EUROs, ERC20MockFactory, admin, user1, user2, user3, protocol, liquidator, ClEurUsd, ClEthUsd, ClCoinUsd, VaultManager, Vault, collateralCoin, SmartVaultIndex;
const rewardAmountForAsset = (rewards, symbol) => {
return rewards.filter(reward => reward.symbol === ethers.utils.formatBytes32String(symbol))[0].amount;
}
beforeEach(async () => {
[admin, user1, user2, user3, protocol, liquidator] = await ethers.getSigners();
ERC20MockFactory = await ethers.getContractFactory('ERC20Mock');
TST = await ERC20MockFactory.deploy('The Standard Token', 'TST', 18);
EUROs = await (await ethers.getContractFactory('EUROsMock')).deploy();
ClEurUsd = await (await ethers.getContractFactory('ChainlinkMock')).deploy('EUR / USD');
await ClEurUsd.setPrice(100000000);
ClEthUsd = await (await ethers.getContractFactory('ChainlinkMock')).deploy('ETH / USD');
await ClEthUsd.setPrice(DEFAULT_ETH_USD_PRICE);
TokenManager = await (await ethers.getContractFactory('TokenManagerMock')).deploy(ETH, ClEthUsd.address);
const SmartVaultDeployer = await (await ethers.getContractFactory('SmartVaultDeployerV3')).deploy(ETH, ClEurUsd.address);
SmartVaultIndex = await (await ethers.getContractFactory('SmartVaultIndex')).deploy();
const NFTMetadataGenerator = await (await getNFTMetadataContract()).deploy();
const SwapRouterMock = await (await ethers.getContractFactory('SwapRouterMock')).deploy();
const MockWeth = await (await ethers.getContractFactory('WETHMock')).deploy();
VaultManager = await fullyUpgradedSmartVaultManager(
DEFAULT_COLLATERAL_RATE, PROTOCOL_FEE_RATE, EUROs.address, protocol.address,
liquidator.address, TokenManager.address, SmartVaultDeployer.address,
SmartVaultIndex.address, NFTMetadataGenerator.address, MockWeth.address,
SwapRouterMock.address
);
await SmartVaultIndex.setVaultManager(VaultManager.address);
await EUROs.grantRole(await EUROs.DEFAULT_ADMIN_ROLE(), VaultManager.address);
await VaultManager.connect(user3).mint();
const _vault = (await VaultManager.connect(user3).vaults())[0];
const vaultAddress = _vault.status.vaultAddress;
Vault = await ethers.getContractAt('SmartVaultV3', vaultAddress);
const LiquidationPoolManagerContract = await ethers.getContractFactory('LiquidationPoolManager');
LiquidationPoolManager = await LiquidationPoolManagerContract.deploy(
TST.address, EUROs.address, VaultManager.address, ClEurUsd.address, protocol.address, POOL_FEE_PERCENTAGE
);
await VaultManager.setLiquidatorAddress(LiquidationPoolManager.address);
await VaultManager.setProtocolAddress(LiquidationPoolManager.address);
LiquidationPool = await ethers.getContractAt('LiquidationPool', await LiquidationPoolManager.pool());
await EUROs.grantRole(await EUROs.BURNER_ROLE(), LiquidationPool.address);
collateralCoin = await ERC20MockFactory.deploy('Collateral Coin', 'cCOIN', 18);
ClCoinUsd = await (await ethers.getContractFactory('ChainlinkMock')).deploy('cCOIN / USD');
await ClCoinUsd.setPrice(DEFAULT_ETH_USD_PRICE);
await TokenManager.addAcceptedToken(collateralCoin.address, ClCoinUsd.address);
await collateralCoin.mint(user3.address, 4);
});
describe('reduced rewards', async () => {
it('rounds-down the rewards', async () => {
const tstStake1 = 3;
const eurosStake1 = 2490;
await TST.mint(user1.address, tstStake1);
await EUROs.mint(user1.address, eurosStake1);
await TST.connect(user1).approve(LiquidationPool.address, tstStake1);
await EUROs.connect(user1).approve(LiquidationPool.address, eurosStake1);
await LiquidationPool.connect(user1).increasePosition(tstStake1, eurosStake1);
const tstStake2 = 2;
const eurosStake2 = 2;
await TST.mint(user2.address, tstStake2);
await EUROs.mint(user2.address, eurosStake2);
await TST.connect(user2).approve(LiquidationPool.address, tstStake2);
await EUROs.connect(user2).approve(LiquidationPool.address, eurosStake2);
await LiquidationPool.connect(user2).increasePosition(tstStake2, eurosStake2);
await fastForward(DAY);
let { _position } = await LiquidationPool.position(user1.address);
expect(_position.TST).to.equal(tstStake1);
expect(_position.EUROs).to.equal(eurosStake1);
({ _position } = await LiquidationPool.position(user2.address));
expect(_position.TST).to.equal(tstStake2);
expect(_position.EUROs).to.equal(eurosStake2);
const userCollateral = 4;
await collateralCoin.connect(user3).mint(Vault.address, userCollateral);
const mintedEUROs = 5000;
await Vault.connect(user3).mint(user3.address, mintedEUROs);
expect(await EUROs.balanceOf(user3.address)).to.equal(mintedEUROs);
await ClCoinUsd.setPrice(150000000000);
await expect(LiquidationPoolManager.connect(user1).runLiquidation(1)).not.to.be.reverted;
const { minted, maxMintable, totalCollateralValue, collateral, liquidated } = await Vault.status();
expect(minted).to.equal(0);
expect(maxMintable).to.equal(0);
expect(totalCollateralValue).to.equal(0);
collateral.forEach(asset => expect(asset.amount).to.equal(0));
expect(liquidated).to.equal(true);
let { _rewards } = await LiquidationPool.position(user1.address);
console.log("reward received by user1 =", rewardAmountForAsset(_rewards, 'cCOIN'));
});
});
});
Loss of reward for the holder.