From 08cc3d269955d218ae3d3edafc020732e4cb32e6 Mon Sep 17 00:00:00 2001 From: Alex Scott <5690430+alsco77@users.noreply.github.com> Date: Tue, 23 Jun 2020 12:21:02 +0200 Subject: [PATCH] Removes the breached requirement from redemption (#87) --- .../masset/forge-validator/ForgeValidator.sol | 22 +++---------- test/masset/TestMassetRedemption.spec.ts | 8 +++-- .../TestForgeValidatorR.spec.ts | 32 ++++++++++++++++--- 3 files changed, 36 insertions(+), 26 deletions(-) diff --git a/contracts/masset/forge-validator/ForgeValidator.sol b/contracts/masset/forge-validator/ForgeValidator.sol index ded6ce74..a6f87fa8 100644 --- a/contracts/masset/forge-validator/ForgeValidator.sol +++ b/contracts/masset/forge-validator/ForgeValidator.sol @@ -10,8 +10,8 @@ import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; * @author Stability Labs Pty. Ltd. * @notice Calculates whether or not minting or redemption is valid, based * on how it affects the underlying basket collateral weightings - * @dev VERSION: 1.0 - * DATE: 2020-05-05 + * @dev VERSION: 1.1 + * DATE: 2020-06-22 */ contract ForgeValidator is IForgeValidator { @@ -222,8 +222,7 @@ contract ForgeValidator is IForgeValidator { // If the basket is in an affected state, enforce proportional redemption if( _basketIsFailed || - data.atLeastOneBroken || - (data.overWeightCount == 0 && data.atLeastOneBreached) + data.atLeastOneBroken ) { return (false, "Must redeem proportionately", false); } else if (data.overWeightCount > idxCount) { @@ -249,7 +248,7 @@ contract ForgeValidator is IForgeValidator { newTotalVault = newTotalVault.sub(ratioedRedemptionAmount); } - // Get overweight/breached after + // Get overweight after bool atLeastOneBecameOverweight = _getOverweightBassetsAfter(newTotalVault, _allBassets, data.ratioedBassetVaults, data.isOverWeight); @@ -333,7 +332,6 @@ contract ForgeValidator is IForgeValidator { bool isValid; string reason; bool atLeastOneBroken; - bool atLeastOneBreached; uint256 overWeightCount; bool[] isOverWeight; uint256[] ratioedBassetVaults; @@ -357,16 +355,11 @@ contract ForgeValidator is IForgeValidator { isValid: true, reason: "", atLeastOneBroken: false, - atLeastOneBreached: false, overWeightCount: 0, isOverWeight: new bool[](len), ratioedBassetVaults: new uint256[](len) }); - uint256 onePercentOfTotal = _total.mulTruncate(1e16); - // Number of units below max a bAsset can be before deemed as breached - uint256 weightBreachThreshold = StableMath.min(onePercentOfTotal, 5e22); - for(uint256 i = 0; i < len; i++) { BassetStatus status = _bAssets[i].status; if(status == BassetStatus.Blacklisted) { @@ -390,13 +383,6 @@ contract ForgeValidator is IForgeValidator { response.isOverWeight[i] = true; response.overWeightCount += 1; } - - // if the bAsset isn't overweight, check if it's within the bound - if(!bAssetOverWeight) { - uint256 lowerBound = weightBreachThreshold > maxWeightInUnits ? 0 : maxWeightInUnits.sub(weightBreachThreshold); - bool isInBound = ratioedBasset > lowerBound && ratioedBasset <= maxWeightInUnits; - response.atLeastOneBreached = response.atLeastOneBreached || isInBound; - } } } diff --git a/test/masset/TestMassetRedemption.spec.ts b/test/masset/TestMassetRedemption.spec.ts index 0859450b..e8e9cd2d 100644 --- a/test/masset/TestMassetRedemption.spec.ts +++ b/test/masset/TestMassetRedemption.spec.ts @@ -756,7 +756,7 @@ contract("Masset - Redeem", async (accounts) => { beforeEach(async () => { await runSetup(false); }); - it("should force proportional redemption no matter what", async () => { + it("should allow redemption as long as nothing goes overweight", async () => { const { bAssets, mAsset, basketManager } = massetDetails; const composition = await massetMachine.getBasketComposition(massetDetails); // Expect 4 bAssets with 100 weightings @@ -782,9 +782,11 @@ contract("Masset - Redeem", async (accounts) => { // Should succeed if we redeem this const bAsset = bAssets[0]; const bAssetDecimals = await bAsset.decimals(); + await assertBasicRedemption(massetDetails, 2, bAsset, true); + // 30% * 93 = 27.8, meaning bAssets[1] is now overweight await expectRevert( - mAsset.redeem(bAsset.address, simpleToExactAmount(10, bAssetDecimals)), - "Must redeem proportionately", + mAsset.redeem(bAsset.address, simpleToExactAmount(5, bAssetDecimals)), + "bAssets must remain below max weight", ); }); }); diff --git a/test/masset/forge-validator/TestForgeValidatorR.spec.ts b/test/masset/forge-validator/TestForgeValidatorR.spec.ts index 95f61e91..3daacef9 100644 --- a/test/masset/forge-validator/TestForgeValidatorR.spec.ts +++ b/test/masset/forge-validator/TestForgeValidatorR.spec.ts @@ -513,7 +513,7 @@ contract("ForgeValidator", async (accounts) => { }); }); context("in a basket with bAssets nearing threshold (max weight breached)", async () => { - it("enforces proportional redemption", async () => { + it("allows redemption as long as nothing goes overweight", async () => { /** * TotalSupply: 100e18 * MaxWeights: [ 40, 40, 40, 40] @@ -530,8 +530,19 @@ contract("ForgeValidator", async (accounts) => { setBasset(40, "10.5"), setBasset(40, 20), ], - [setArgs(0, 1)], - setResult(false, "Must redeem proportionately"), + [setArgs(0, 10)], + setResult(true, "", true), + ); + await assertRedeem( + setBasket(false, 100), + [ + setBasset(40, "39.5"), + setBasset(40, 30), + setBasset(40, "10.5"), + setBasset(40, 20), + ], + [setArgs(1, 3)], + setResult(false, "bAssets must remain below max weight"), ); }); describe("and using multiple inputs", async () => { @@ -552,8 +563,19 @@ contract("ForgeValidator", async (accounts) => { setBasset(40, "10.5"), setBasset(40, 20), ], - [setArgs(0, 1), setArgs(3, 3)], - setResult(false, "Must redeem proportionately"), + [setArgs(0, 5), setArgs(3, 3)], + setResult(true, "", true), + ); + await assertRedeem( + setBasket(false, 100), + [ + setBasset(40, "39.5"), + setBasset(40, 30), + setBasset(40, "10.5"), + setBasset(40, 20), + ], + [setArgs(0, 1), setArgs(1, 7)], + setResult(false, "bAssets must remain below max weight"), ); }); });