From 4f56a021111f3c2c8f9610f37f29e7c8e226c7f1 Mon Sep 17 00:00:00 2001 From: Carl Farterson Date: Mon, 8 Nov 2021 12:22:19 -0800 Subject: [PATCH 1/2] feat: calculateRawBurnReturn() --- contracts/Foundry.sol | 148 +++++++++++++++++++++++++----------------- 1 file changed, 89 insertions(+), 59 deletions(-) diff --git a/contracts/Foundry.sol b/contracts/Foundry.sol index 1af5b179..93b2b7d5 100644 --- a/contracts/Foundry.sol +++ b/contracts/Foundry.sol @@ -129,85 +129,54 @@ contract Foundry is IFoundry, Ownable, Initializable { } // Calculate how many tokens tokens are returned - uint256 tokensReturned = calculateBurnReturn(_meToken, _meTokensBurned); - - uint256 feeRate; - uint256 actualTokensReturned; - // If msg.sender == owner, give owner the sell rate. - all of tokens returned plus a % - // of balancePooled based on how much % of supply will be burned - // If msg.sender != owner, give msg.sender the burn rate - if (msg.sender == meToken_.owner) { - feeRate = fees.burnOwnerFee(); - actualTokensReturned = - tokensReturned + - (((PRECISION * _meTokensBurned) / - IERC20(_meToken).totalSupply()) * meToken_.balanceLocked) / - PRECISION; - } else { - feeRate = fees.burnBuyerFee(); - if (hub_.targetRefundRatio == 0 && meToken_.targetHubId == 0) { - // Not updating targetRefundRatio or resubscribing - actualTokensReturned = - (tokensReturned * hub_.refundRatio) / - MAX_REFUND_RATIO; - } else { - if (hub_.targetRefundRatio > 0) { - // Hub is updating - actualTokensReturned = - (tokensReturned * - WeightedAverage.calculate( - hub_.refundRatio, - hub_.targetRefundRatio, - hub_.startTime, - hub_.endTime - )) / - MAX_REFUND_RATIO; - } else { - // meToken is resubscribing - Details.Hub memory targetHub_ = hub.getDetails( - meToken_.targetHubId - ); - actualTokensReturned = - (tokensReturned * - WeightedAverage.calculate( - hub_.refundRatio, - targetHub_.refundRatio, - meToken_.startTime, - meToken_.endTime - )) / - MAX_REFUND_RATIO; - } - } - } + // NOTE: very inefficient + uint256 rawTokensReturned = calculateRawBurnReturn( + _meToken, + _meTokensBurned + ); + // NOTE: actualTokensReturned does not account for fees + uint256 actualTokensReturned = calculateBurnReturn( + msg.sender, + _meToken, + _meTokensBurned + ); // Burn metoken from user IERC20(_meToken).burn(msg.sender, _meTokensBurned); // Subtract tokens returned from balance pooled - meTokenRegistry.updateBalancePooled(false, _meToken, tokensReturned); + meTokenRegistry.updateBalancePooled(false, _meToken, rawTokensReturned); + + uint256 fee; + if (msg.sender == meToken_.owner) { + fee = actualTokensReturned * fees.burnOwnerFee(); + } else { + fee = actualTokensReturned * fees.burnBuyerFee(); + } + // NOTE: if actualTokensReturned == rawTokensReturned, that means owner + // has burned before any buyers have burned, in which case balanceLocked = 0 + // if (actualTokensReturned > rawTokensReturned) { if (msg.sender == meToken_.owner) { // Is owner, subtract from balance locked meTokenRegistry.updateBalanceLocked( false, _meToken, - actualTokensReturned - tokensReturned + actualTokensReturned - rawTokensReturned ); } else { // Is buyer, add to balance locked using refund ratio meTokenRegistry.updateBalanceLocked( true, _meToken, - tokensReturned - actualTokensReturned + rawTokensReturned - actualTokensReturned - fee ); } - uint256 fee = actualTokensReturned * feeRate; - actualTokensReturned -= fee; IERC20(hub_.asset).transferFrom( hub_.vault, _recipient, - actualTokensReturned + actualTokensReturned - fee ); IVault(hub_.vault).addFee(hub_.asset, fee); @@ -296,18 +265,18 @@ contract Foundry is IFoundry, Ownable, Initializable { } } - function calculateBurnReturn(address _meToken, uint256 _meTokensBurned) + function calculateRawBurnReturn(address _meToken, uint256 _meTokensBurned) public view returns (uint256 tokensReturned) { Details.MeToken memory meToken_ = meTokenRegistry.getDetails(_meToken); Details.Hub memory hub_ = hub.getDetails(meToken_.hubId); - // gas savings + uint256 totalSupply_ = IERC20(_meToken).totalSupply(); // Calculate return assuming update is not happening - tokensReturned = ICurve(hub_.curve).calculateBurnReturn( + uint256 tokensReturned = ICurve(hub_.curve).calculateBurnReturn( _meTokensBurned, meToken_.hubId, totalSupply_, @@ -347,4 +316,65 @@ contract Foundry is IFoundry, Ownable, Initializable { ); } } + + function calculateBurnReturn( + address _sender, + address _meToken, + uint256 _meTokensBurned + ) public view returns (uint256 actualTokensReturned) { + Details.MeToken memory meToken_ = meTokenRegistry.getDetails(_meToken); + Details.Hub memory hub_ = hub.getDetails(meToken_.hubId); + + uint256 tokensReturned = calculateRawBurnReturn( + _meToken, + _meTokensBurned + ); + + // If msg.sender == owner, give owner the sell rate. - all of tokens returned plus a % + // of balancePooled based on how much % of supply will be burned + // aka. meTokensBurned / supply * balanceLocked + // If msg.sender != owner, give msg.sender the burn rate + if (_sender == meToken_.owner) { + actualTokensReturned = + tokensReturned + + (PRECISION * _meTokensBurned * meToken_.balanceLocked) / + IERC20(_meToken).totalSupply() / + PRECISION; + } else { + // Not owner, subtract refundRatio + if (hub_.targetRefundRatio == 0 && meToken_.targetHubId == 0) { + // Not updating refundRatio of hub or resusbscribing meToken + actualTokensReturned = + (tokensReturned * hub_.refundRatio) / + MAX_REFUND_RATIO; + } else { + if (hub_.targetRefundRatio > 0) { + // Hub is updating + actualTokensReturned = + (tokensReturned * + WeightedAverage.calculate( + hub_.refundRatio, + hub_.targetRefundRatio, + hub_.startTime, + hub_.endTime + )) / + MAX_REFUND_RATIO; + } else { + // meToken is resubscribing + Details.Hub memory targetHub_ = hub.getDetails( + meToken_.targetHubId + ); + actualTokensReturned = + (tokensReturned * + WeightedAverage.calculate( + hub_.refundRatio, + targetHub_.refundRatio, + meToken_.startTime, + meToken_.endTime + )) / + MAX_REFUND_RATIO; + } + } + } + } } From e13b8fd99dd63404700f953fedca98dffa1cb3fc Mon Sep 17 00:00:00 2001 From: Carl Farterson Date: Mon, 8 Nov 2021 12:37:00 -0800 Subject: [PATCH 2/2] fix: replace raw func with viewBurn() --- contracts/Foundry.sol | 209 ++++++++++++++++++++++++------------------ 1 file changed, 120 insertions(+), 89 deletions(-) diff --git a/contracts/Foundry.sol b/contracts/Foundry.sol index 93b2b7d5..b8b01418 100644 --- a/contracts/Foundry.sol +++ b/contracts/Foundry.sol @@ -129,54 +129,85 @@ contract Foundry is IFoundry, Ownable, Initializable { } // Calculate how many tokens tokens are returned - // NOTE: very inefficient - uint256 rawTokensReturned = calculateRawBurnReturn( - _meToken, - _meTokensBurned - ); - // NOTE: actualTokensReturned does not account for fees - uint256 actualTokensReturned = calculateBurnReturn( - msg.sender, - _meToken, - _meTokensBurned - ); + uint256 tokensReturned = calculateBurnReturn(_meToken, _meTokensBurned); + + uint256 feeRate; + uint256 actualTokensReturned; + // If msg.sender == owner, give owner the sell rate. - all of tokens returned plus a % + // of balancePooled based on how much % of supply will be burned + // If msg.sender != owner, give msg.sender the burn rate + if (msg.sender == meToken_.owner) { + feeRate = fees.burnOwnerFee(); + actualTokensReturned = + tokensReturned + + (((PRECISION * _meTokensBurned) / + IERC20(_meToken).totalSupply()) * meToken_.balanceLocked) / + PRECISION; + } else { + feeRate = fees.burnBuyerFee(); + if (hub_.targetRefundRatio == 0 && meToken_.targetHubId == 0) { + // Not updating targetRefundRatio or resubscribing + actualTokensReturned = + (tokensReturned * hub_.refundRatio) / + MAX_REFUND_RATIO; + } else { + if (hub_.targetRefundRatio > 0) { + // Hub is updating + actualTokensReturned = + (tokensReturned * + WeightedAverage.calculate( + hub_.refundRatio, + hub_.targetRefundRatio, + hub_.startTime, + hub_.endTime + )) / + MAX_REFUND_RATIO; + } else { + // meToken is resubscribing + Details.Hub memory targetHub_ = hub.getDetails( + meToken_.targetHubId + ); + actualTokensReturned = + (tokensReturned * + WeightedAverage.calculate( + hub_.refundRatio, + targetHub_.refundRatio, + meToken_.startTime, + meToken_.endTime + )) / + MAX_REFUND_RATIO; + } + } + } // Burn metoken from user IERC20(_meToken).burn(msg.sender, _meTokensBurned); // Subtract tokens returned from balance pooled - meTokenRegistry.updateBalancePooled(false, _meToken, rawTokensReturned); - - uint256 fee; - if (msg.sender == meToken_.owner) { - fee = actualTokensReturned * fees.burnOwnerFee(); - } else { - fee = actualTokensReturned * fees.burnBuyerFee(); - } + meTokenRegistry.updateBalancePooled(false, _meToken, tokensReturned); - // NOTE: if actualTokensReturned == rawTokensReturned, that means owner - // has burned before any buyers have burned, in which case balanceLocked = 0 - // if (actualTokensReturned > rawTokensReturned) { if (msg.sender == meToken_.owner) { // Is owner, subtract from balance locked meTokenRegistry.updateBalanceLocked( false, _meToken, - actualTokensReturned - rawTokensReturned + actualTokensReturned - tokensReturned ); } else { // Is buyer, add to balance locked using refund ratio meTokenRegistry.updateBalanceLocked( true, _meToken, - rawTokensReturned - actualTokensReturned - fee + tokensReturned - actualTokensReturned ); } + uint256 fee = actualTokensReturned * feeRate; + actualTokensReturned -= fee; IERC20(hub_.asset).transferFrom( hub_.vault, _recipient, - actualTokensReturned - fee + actualTokensReturned ); IVault(hub_.vault).addFee(hub_.asset, fee); @@ -265,85 +296,33 @@ contract Foundry is IFoundry, Ownable, Initializable { } } - function calculateRawBurnReturn(address _meToken, uint256 _meTokensBurned) - public - view - returns (uint256 tokensReturned) - { - Details.MeToken memory meToken_ = meTokenRegistry.getDetails(_meToken); - Details.Hub memory hub_ = hub.getDetails(meToken_.hubId); - - uint256 totalSupply_ = IERC20(_meToken).totalSupply(); - - // Calculate return assuming update is not happening - uint256 tokensReturned = ICurve(hub_.curve).calculateBurnReturn( - _meTokensBurned, - meToken_.hubId, - totalSupply_, - meToken_.balancePooled - ); - - // Logic for if we're switching to a new curve type // updating curveDetails - if ( - (hub_.updating && (hub_.targetCurve != address(0))) || - (hub_.reconfigure) - ) { - uint256 targetTokensReturned; - if (hub_.targetCurve != address(0)) { - // Means we are updating to a new curve type - targetTokensReturned = ICurve(hub_.targetCurve) - .calculateBurnReturn( - _meTokensBurned, - meToken_.hubId, - totalSupply_, - meToken_.balancePooled - ); - } else { - // Must mean we're updating curveDetails - targetTokensReturned = ICurve(hub_.curve) - .calculateTargetBurnReturn( - _meTokensBurned, - meToken_.hubId, - totalSupply_, - meToken_.balancePooled - ); - } - tokensReturned = WeightedAverage.calculate( - tokensReturned, - targetTokensReturned, - hub_.startTime, - hub_.endTime - ); - } - } - - function calculateBurnReturn( + function viewBurn( address _sender, address _meToken, uint256 _meTokensBurned - ) public view returns (uint256 actualTokensReturned) { + ) external view returns (uint256 actualtokensReturned) { Details.MeToken memory meToken_ = meTokenRegistry.getDetails(_meToken); Details.Hub memory hub_ = hub.getDetails(meToken_.hubId); - uint256 tokensReturned = calculateRawBurnReturn( - _meToken, - _meTokensBurned - ); + // Calculate how many tokens tokens are returned + uint256 tokensReturned = calculateBurnReturn(_meToken, _meTokensBurned); + uint256 feeRate; + uint256 actualTokensReturned; // If msg.sender == owner, give owner the sell rate. - all of tokens returned plus a % // of balancePooled based on how much % of supply will be burned - // aka. meTokensBurned / supply * balanceLocked // If msg.sender != owner, give msg.sender the burn rate - if (_sender == meToken_.owner) { + if (msg.sender == meToken_.owner) { + feeRate = fees.burnOwnerFee(); actualTokensReturned = tokensReturned + - (PRECISION * _meTokensBurned * meToken_.balanceLocked) / - IERC20(_meToken).totalSupply() / + (((PRECISION * _meTokensBurned) / + IERC20(_meToken).totalSupply()) * meToken_.balanceLocked) / PRECISION; } else { - // Not owner, subtract refundRatio + feeRate = fees.burnBuyerFee(); if (hub_.targetRefundRatio == 0 && meToken_.targetHubId == 0) { - // Not updating refundRatio of hub or resusbscribing meToken + // Not updating targetRefundRatio or resubscribing actualTokensReturned = (tokensReturned * hub_.refundRatio) / MAX_REFUND_RATIO; @@ -377,4 +356,56 @@ contract Foundry is IFoundry, Ownable, Initializable { } } } + + function calculateBurnReturn(address _meToken, uint256 _meTokensBurned) + public + view + returns (uint256 tokensReturned) + { + Details.MeToken memory meToken_ = meTokenRegistry.getDetails(_meToken); + Details.Hub memory hub_ = hub.getDetails(meToken_.hubId); + // gas savings + uint256 totalSupply_ = IERC20(_meToken).totalSupply(); + + // Calculate return assuming update is not happening + tokensReturned = ICurve(hub_.curve).calculateBurnReturn( + _meTokensBurned, + meToken_.hubId, + totalSupply_, + meToken_.balancePooled + ); + + // Logic for if we're switching to a new curve type // updating curveDetails + if ( + (hub_.updating && (hub_.targetCurve != address(0))) || + (hub_.reconfigure) + ) { + uint256 targetTokensReturned; + if (hub_.targetCurve != address(0)) { + // Means we are updating to a new curve type + targetTokensReturned = ICurve(hub_.targetCurve) + .calculateBurnReturn( + _meTokensBurned, + meToken_.hubId, + totalSupply_, + meToken_.balancePooled + ); + } else { + // Must mean we're updating curveDetails + targetTokensReturned = ICurve(hub_.curve) + .calculateTargetBurnReturn( + _meTokensBurned, + meToken_.hubId, + totalSupply_, + meToken_.balancePooled + ); + } + tokensReturned = WeightedAverage.calculate( + tokensReturned, + targetTokensReturned, + hub_.startTime, + hub_.endTime + ); + } + } }