From 697c13d837e9de7b7a4c721ba1f8238cb1c2ea73 Mon Sep 17 00:00:00 2001 From: Richard Liang Date: Mon, 11 Oct 2021 11:54:22 -0700 Subject: [PATCH 01/10] Checkpoint 1 Trade Rebates --- STIPS/STIP-005.md | 122 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 STIPS/STIP-005.md diff --git a/STIPS/STIP-005.md b/STIPS/STIP-005.md new file mode 100644 index 0000000..0a13cfb --- /dev/null +++ b/STIPS/STIP-005.md @@ -0,0 +1,122 @@ +# STIP-005 +*Using template v0.1* +## Abstract +The current TradeModule and GeneralIndexModule do not support volume based fees or rebates for the manager. This is in contrast to the issuance module. There is demand for additional fee types that managers can charge, specifically to be on par with exchanges where there are rebate / volume fee discounts. + +## Motivation +This feature is a new version of a TradeModule (and GeneralIndexModule) that allows the manager to charge a volume fee from their followers. This will be denominated in the output token similar to the protocol fee assessed. + +TradeModuleV2 will be used in upcoming TokenSets deployments on other chains, and can replace the existing on Polygon and Mainnet. GeneralIndexModuleV2 can be used by index coop products to generate additional revenue + +## Background Information +We have previously implemented the TradeModule and GeneralIndexModule. This will be a few lines code change to allow manager volume fees charged on the output token + +## Open Questions +- [ ] Should we ensure that manager must have capital in the Set to charge volume fees? + - No, we can encourage in the UI instead for retail managers. This would also break for cases where the manager is a smart contract + +## Feasibility Analysis + +#### Option 1 +Adding a managerFee % which is assessed on top of the protocol fee %. This will be least code changes to the TradeModule and General Index Module. To avoid rugging, there will be a maxManagerFee enforced. +``` +function _accrueProtocolFee(TradeInfo memory _tradeInfo, uint256 _exchangedQuantity) internal returns (uint256) { + uint256 protocolFeeTotal = getModuleFee(TRADE_MODULE_PROTOCOL_FEE_INDEX, _exchangedQuantity); + + payProtocolFeeFromSetToken(_tradeInfo.setToken, _tradeInfo.receiveToken, protocolFeeTotal); + + return protocolFeeTotal; +} +``` + +#### Option 2 +Adding a managerFee and a protocol feeSplit % (similar to DebtIssuanceModule). This will make TradeModule and GeneralIndexModule fees variable in the protocol (if a manager charges 0 fees here, then protocol will always make 0) which is a departure from TradeModule V1 + +``` +uint256 protocolFeeSplit = controller.getModuleFee(address(this), ISSUANCE_MODULE_PROTOCOL_FEE_SPLIT_INDEX); +uint256 totalFeeRate = _isIssue ? setIssuanceSettings.managerIssueFee : setIssuanceSettings.managerRedeemFee; + +uint256 totalFee = totalFeeRate.preciseMul(_quantity); +protocolFee = totalFee.preciseMul(protocolFeeSplit); +managerFee = totalFee.sub(protocolFee); +``` + +#### Option 3 +Manager charges a managerFee that is split with the protocol based on a feeSplit % determined by governance. Additionally, the protocol can charge a direct fee % (similar to NAVIssuanceModule). This will be a superset of TradeModule fees charged + +``` +uint256 protocolFeeSplit = controller.getModuleFee(address(this), ISSUANCE_MODULE_PROTOCOL_FEE_SPLIT_INDEX); +uint256 totalFeeRate = _isIssue ? setIssuanceSettings.managerIssueFee : setIssuanceSettings.managerRedeemFee; + +uint256 totalFee = totalFeeRate.preciseMul(_quantity); +protocolFee = totalFee.preciseMul(protocolFeeSplit); +managerFee = totalFee.sub(protocolFee); +``` + +#### Recommendation +Option 1 will require the least code changes while giving us the ability to emulate fee rebates. The maxManagerFee can be enforced either on initialization (still susceptible to manager rugging by removing and reinitializing) or stored on the Controller by governance + +## Timeline +1 day spec +2 day implementation +1 day review +1 day deploy script +1 day testing + +## Checkpoint 1 +Before more in depth design of the contract flows lets make sure that all the work done to this point has been exhaustive. It should be clear what we're doing, why, and for who. All necessary information on external protocols should be gathered and potential solutions considered. At this point we should be in alignment with product on the non-technical requirements for this feature. It is up to the reviewer to determine whether we move onto the next step. + +**Reviewer**: + +## Proposed Architecture Changes +A diagram would be helpful here to see where new feature slot into the system. Additionally a brief description of any new contracts is helpful. +## Requirements +These should be a distillation of the previous two sections taking into account the decided upon high-level implementation. Each flow should have high level requirements taking into account the needs of participants in the flow (users, managers, market makers, app devs, etc) +## User Flows +- Highlight *each* external flow enabled by this feature. It's helpful to use diagrams (add them to the `assets` folder). Examples can be very helpful, make sure to highlight *who* is initiating this flow, *when* and *why*. A reviewer should be able to pick out what requirements are being covered by this flow. +## Checkpoint 2 +Before we spec out the contract(s) in depth we want to make sure that we are aligned on all the technical requirements and flows for contract interaction. Again the who, what, when, why should be clearly illuminated for each flow. It is up to the reviewer to determine whether we move onto the next step. + +**Reviewer**: + +Reviewer: [] +## Specification +### [Contract Name] +#### Inheritance +- List inherited contracts +#### Structs +| Type | Name | Description | +|------ |------ |------------- | +|address|manager|Address of the manager| +|uint256|iterations|Number of times manager has called contract| +#### Constants +| Type | Name | Description | Value | +|------ |------ |------------- |------- | +|uint256|ONE | The number one| 1 | +#### Public Variables +| Type | Name | Description | +|------ |------ |------------- | +|uint256|hodlers|Number of holders of this token| +#### Functions +| Name | Caller | Description | +|------ |------ |------------- | +|startRebalance|Manager|Set rebalance parameters| +|rebalance|Trader|Rebalance SetToken| +|ripcord|EOA|Recenter leverage ratio| +#### Modifiers +> onlyManager(SetToken _setToken) +#### Functions +> issue(SetToken _setToken, uint256 quantity) external +- Pseudo code +## Checkpoint 3 +Before we move onto the implementation phase we want to make sure that we are aligned on the spec. All contracts should be specced out, their state and external function signatures should be defined. For more complex contracts, internal function definition is preferred in order to align on proper abstractions. Reviewer should take care to make sure that all stake holders (product, app engineering) have their needs met in this stage. + +**Reviewer**: + +## Implementation +[Link to implementation PR]() +## Documentation +[Link to Documentation on feature]() +## Deployment +[Link to Deployment script PR]() +[Link to Deploy outputs PR]() From 1fe9b922785b59bbf5fa8dc0185b5d86dcffb464 Mon Sep 17 00:00:00 2001 From: Richard Liang Date: Mon, 11 Oct 2021 11:55:15 -0700 Subject: [PATCH 02/10] Add open question --- STIPS/STIP-005.md | 1 + 1 file changed, 1 insertion(+) diff --git a/STIPS/STIP-005.md b/STIPS/STIP-005.md index 0a13cfb..6cc2694 100644 --- a/STIPS/STIP-005.md +++ b/STIPS/STIP-005.md @@ -14,6 +14,7 @@ We have previously implemented the TradeModule and GeneralIndexModule. This will ## Open Questions - [ ] Should we ensure that manager must have capital in the Set to charge volume fees? - No, we can encourage in the UI instead for retail managers. This would also break for cases where the manager is a smart contract +- [ ] Should we name this TradeModuleV2 or TradeModuleWithRebates? ## Feasibility Analysis From 89d5b7ae41cb9fd52610b2b59611bfed0f8c1ad9 Mon Sep 17 00:00:00 2001 From: Richard Liang Date: Mon, 11 Oct 2021 12:02:52 -0700 Subject: [PATCH 03/10] Fix timeline --- STIPS/STIP-005.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/STIPS/STIP-005.md b/STIPS/STIP-005.md index 6cc2694..392363f 100644 --- a/STIPS/STIP-005.md +++ b/STIPS/STIP-005.md @@ -58,11 +58,13 @@ managerFee = totalFee.sub(protocolFee); Option 1 will require the least code changes while giving us the ability to emulate fee rebates. The maxManagerFee can be enforced either on initialization (still susceptible to manager rugging by removing and reinitializing) or stored on the Controller by governance ## Timeline -1 day spec -2 day implementation -1 day review -1 day deploy script -1 day testing +- Spec + review: 2 days +- Implementation: 2 days +- Internal review: 1 days +- Deployment scripts: 1 day +- Deploy to testnet: 1 day +- Testing: 1 day +- Write docs: 1 day ## Checkpoint 1 Before more in depth design of the contract flows lets make sure that all the work done to this point has been exhaustive. It should be clear what we're doing, why, and for who. All necessary information on external protocols should be gathered and potential solutions considered. At this point we should be in alignment with product on the non-technical requirements for this feature. It is up to the reviewer to determine whether we move onto the next step. From 14c95969d460421ea3145b6f7457a8f95fbda8a4 Mon Sep 17 00:00:00 2001 From: Richard Liang Date: Mon, 11 Oct 2021 12:13:46 -0700 Subject: [PATCH 04/10] Add additional context --- STIPS/STIP-005.md | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/STIPS/STIP-005.md b/STIPS/STIP-005.md index 392363f..80e44c6 100644 --- a/STIPS/STIP-005.md +++ b/STIPS/STIP-005.md @@ -4,30 +4,27 @@ The current TradeModule and GeneralIndexModule do not support volume based fees or rebates for the manager. This is in contrast to the issuance module. There is demand for additional fee types that managers can charge, specifically to be on par with exchanges where there are rebate / volume fee discounts. ## Motivation -This feature is a new version of a TradeModule (and GeneralIndexModule) that allows the manager to charge a volume fee from their followers. This will be denominated in the output token similar to the protocol fee assessed. +This feature is a new version of a TradeModule (and GeneralIndexModule) that allows the manager to charge a volume fee from their followers. This will be denominated in the output token similar to the protocol fee assessed. In contrast to centralized exchanges, the fee rebates will be automatic per trade. TradeModuleV2 will be used in upcoming TokenSets deployments on other chains, and can replace the existing on Polygon and Mainnet. GeneralIndexModuleV2 can be used by index coop products to generate additional revenue ## Background Information -We have previously implemented the TradeModule and GeneralIndexModule. This will be a few lines code change to allow manager volume fees charged on the output token +We have previously implemented the TradeModule and GeneralIndexModule. This will be an update to allow manager trade rebates charged on the output token. We have previously implemented modules for both manager and protocol fees (NAVIssuanceModule, DebtIssuanceModule) with differing mechanisms ## Open Questions - [ ] Should we ensure that manager must have capital in the Set to charge volume fees? - No, we can encourage in the UI instead for retail managers. This would also break for cases where the manager is a smart contract -- [ ] Should we name this TradeModuleV2 or TradeModuleWithRebates? +- [ ] Should we name this TradeModuleV2 or TradeModuleWithRebates (GeneralIndexModuleV2 or GeneralIndexModuleWithRebates)? +- [ ] How do we prevent managers charging an 100% fee and rugging the Set on a trade? ## Feasibility Analysis #### Option 1 Adding a managerFee % which is assessed on top of the protocol fee %. This will be least code changes to the TradeModule and General Index Module. To avoid rugging, there will be a maxManagerFee enforced. ``` -function _accrueProtocolFee(TradeInfo memory _tradeInfo, uint256 _exchangedQuantity) internal returns (uint256) { - uint256 protocolFeeTotal = getModuleFee(TRADE_MODULE_PROTOCOL_FEE_INDEX, _exchangedQuantity); - - payProtocolFeeFromSetToken(_tradeInfo.setToken, _tradeInfo.receiveToken, protocolFeeTotal); - - return protocolFeeTotal; -} +uint256 protocolFeeTotal = getModuleFee(TRADE_MODULE_PROTOCOL_FEE_INDEX, _exchangedQuantity); + +// Get manager fee here ``` #### Option 2 @@ -46,12 +43,7 @@ managerFee = totalFee.sub(protocolFee); Manager charges a managerFee that is split with the protocol based on a feeSplit % determined by governance. Additionally, the protocol can charge a direct fee % (similar to NAVIssuanceModule). This will be a superset of TradeModule fees charged ``` -uint256 protocolFeeSplit = controller.getModuleFee(address(this), ISSUANCE_MODULE_PROTOCOL_FEE_SPLIT_INDEX); -uint256 totalFeeRate = _isIssue ? setIssuanceSettings.managerIssueFee : setIssuanceSettings.managerRedeemFee; - -uint256 totalFee = totalFeeRate.preciseMul(_quantity); -protocolFee = totalFee.preciseMul(protocolFeeSplit); -managerFee = totalFee.sub(protocolFee); +uint256 protocolDirectFeePercent = controller.getModuleFee(address(this), _protocolDirectFeeIndex); ``` #### Recommendation From 1e0bdd21cc6ceb3d4569c7d17e45c30fca6d55cc Mon Sep 17 00:00:00 2001 From: Richard Liang Date: Mon, 11 Oct 2021 21:53:55 -0700 Subject: [PATCH 05/10] Update STIP --- STIPS/{STIP-005.md => STIP-006.md} | 34 ++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) rename STIPS/{STIP-005.md => STIP-006.md} (82%) diff --git a/STIPS/STIP-005.md b/STIPS/STIP-006.md similarity index 82% rename from STIPS/STIP-005.md rename to STIPS/STIP-006.md index 80e44c6..ded9709 100644 --- a/STIPS/STIP-005.md +++ b/STIPS/STIP-006.md @@ -1,4 +1,4 @@ -# STIP-005 +# STIP-006 *Using template v0.1* ## Abstract The current TradeModule and GeneralIndexModule do not support volume based fees or rebates for the manager. This is in contrast to the issuance module. There is demand for additional fee types that managers can charge, specifically to be on par with exchanges where there are rebate / volume fee discounts. @@ -46,8 +46,19 @@ Manager charges a managerFee that is split with the protocol based on a feeSplit uint256 protocolDirectFeePercent = controller.getModuleFee(address(this), _protocolDirectFeeIndex); ``` +#### Option 4 +Governance specifies a protocol fee % and a rebate split %. The split gives the manager a rebate on a percentage of protocol fees. Manager has no say and cannot rug. + +``` +uint256 protocolFeeSplit = controller.getModuleFee(address(this), PROTOCOL_FEE_SPLIT_INDEX); + +uint256 protocolDirectFee = controller.getModuleFee(address(this), PROTOCOL_DIRECT_FEE_SPLIT_INDEX); +protocolFee = protocolDirectFee.preciseMul(protocolFeeSplit); +rebateFee = protocolDirectFee.sub(protocolFee); +``` + #### Recommendation -Option 1 will require the least code changes while giving us the ability to emulate fee rebates. The maxManagerFee can be enforced either on initialization (still susceptible to manager rugging by removing and reinitializing) or stored on the Controller by governance +Option 4 gives us the most protection against rugging while functioning as a rebate mechanism. This means we will charge a protocol fee to start in order to activate rebates. E.g. start with a 10 bps protocol fee and 5 bps rebate ## Timeline - Spec + review: 2 days @@ -64,9 +75,24 @@ Before more in depth design of the contract flows lets make sure that all the wo **Reviewer**: ## Proposed Architecture Changes -A diagram would be helpful here to see where new feature slot into the system. Additionally a brief description of any new contracts is helpful. + +### TradeModuleV2 +- Inherit TradeModule +- Override `trade` +- Add `virtual` to TradeModule V1 +- Add `_accrueManagerFee` +- Override constructor to add `maxManagerRebateFee`, `managerRebateFee` and `feeRecipient` + +### GeneralIndexModuleV2 +- Inherit GeneralIndexModule +- Override `trade` and `tradeRemainingWETH` +- Add `virtual` to GeneralIndexModule trade functions +- Add `_accrueManagerFee` +- Override constructor to add `maxManagerRebateFee`, `managerRebateFee` and `feeRecipient` + ## Requirements -These should be a distillation of the previous two sections taking into account the decided upon high-level implementation. Each flow should have high level requirements taking into account the needs of participants in the flow (users, managers, market makers, app devs, etc) +- GeneralIndexModule requires no changes to the IC extension contracts except a redeployment + ## User Flows - Highlight *each* external flow enabled by this feature. It's helpful to use diagrams (add them to the `assets` folder). Examples can be very helpful, make sure to highlight *who* is initiating this flow, *when* and *why*. A reviewer should be able to pick out what requirements are being covered by this flow. ## Checkpoint 2 From 6a61535123af16cb04f943fcbece86143d3d1cfa Mon Sep 17 00:00:00 2001 From: Richard Liang Date: Mon, 11 Oct 2021 21:59:08 -0700 Subject: [PATCH 06/10] Update STIP with new architecture --- STIPS/STIP-006.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/STIPS/STIP-006.md b/STIPS/STIP-006.md index ded9709..24887a1 100644 --- a/STIPS/STIP-006.md +++ b/STIPS/STIP-006.md @@ -4,7 +4,7 @@ The current TradeModule and GeneralIndexModule do not support volume based fees or rebates for the manager. This is in contrast to the issuance module. There is demand for additional fee types that managers can charge, specifically to be on par with exchanges where there are rebate / volume fee discounts. ## Motivation -This feature is a new version of a TradeModule (and GeneralIndexModule) that allows the manager to charge a volume fee from their followers. This will be denominated in the output token similar to the protocol fee assessed. In contrast to centralized exchanges, the fee rebates will be automatic per trade. +This feature is a new version of a TradeModule (and GeneralIndexModule) that allows governance to specify a protocol fee and rebate split percentage which is sent to the manager's address. This will be denominated in the output token similar to the protocol fee assessed. The fee rebates will be automatic per trade. TradeModuleV2 will be used in upcoming TokenSets deployments on other chains, and can replace the existing on Polygon and Mainnet. GeneralIndexModuleV2 can be used by index coop products to generate additional revenue @@ -16,6 +16,7 @@ We have previously implemented the TradeModule and GeneralIndexModule. This will - No, we can encourage in the UI instead for retail managers. This would also break for cases where the manager is a smart contract - [ ] Should we name this TradeModuleV2 or TradeModuleWithRebates (GeneralIndexModuleV2 or GeneralIndexModuleWithRebates)? - [ ] How do we prevent managers charging an 100% fee and rugging the Set on a trade? + - We will only allow governance to change rebate %s ## Feasibility Analysis @@ -81,18 +82,19 @@ Before more in depth design of the contract flows lets make sure that all the wo - Override `trade` - Add `virtual` to TradeModule V1 - Add `_accrueManagerFee` -- Override constructor to add `maxManagerRebateFee`, `managerRebateFee` and `feeRecipient` +- Override constructor to add `managerFeeRecipient` ### GeneralIndexModuleV2 - Inherit GeneralIndexModule - Override `trade` and `tradeRemainingWETH` - Add `virtual` to GeneralIndexModule trade functions - Add `_accrueManagerFee` -- Override constructor to add `maxManagerRebateFee`, `managerRebateFee` and `feeRecipient` +- Override constructor to add `managerFeeRecipient` ## Requirements - GeneralIndexModule requires no changes to the IC extension contracts except a redeployment + ## User Flows - Highlight *each* external flow enabled by this feature. It's helpful to use diagrams (add them to the `assets` folder). Examples can be very helpful, make sure to highlight *who* is initiating this flow, *when* and *why*. A reviewer should be able to pick out what requirements are being covered by this flow. ## Checkpoint 2 From 9bf49db2e75589d5036240e35398676f2b07397b Mon Sep 17 00:00:00 2001 From: Richard Liang Date: Tue, 12 Oct 2021 15:03:34 -0700 Subject: [PATCH 07/10] Add checkpoint 2 --- STIPS/STIP-006.md | 44 +++++++++++++++------------------- assets/tradeModuleRebates.png | Bin 0 -> 31377 bytes 2 files changed, 19 insertions(+), 25 deletions(-) create mode 100644 assets/tradeModuleRebates.png diff --git a/STIPS/STIP-006.md b/STIPS/STIP-006.md index 24887a1..f8da141 100644 --- a/STIPS/STIP-006.md +++ b/STIPS/STIP-006.md @@ -29,25 +29,6 @@ uint256 protocolFeeTotal = getModuleFee(TRADE_MODULE_PROTOCOL_FEE_INDEX, _exchan ``` #### Option 2 -Adding a managerFee and a protocol feeSplit % (similar to DebtIssuanceModule). This will make TradeModule and GeneralIndexModule fees variable in the protocol (if a manager charges 0 fees here, then protocol will always make 0) which is a departure from TradeModule V1 - -``` -uint256 protocolFeeSplit = controller.getModuleFee(address(this), ISSUANCE_MODULE_PROTOCOL_FEE_SPLIT_INDEX); -uint256 totalFeeRate = _isIssue ? setIssuanceSettings.managerIssueFee : setIssuanceSettings.managerRedeemFee; - -uint256 totalFee = totalFeeRate.preciseMul(_quantity); -protocolFee = totalFee.preciseMul(protocolFeeSplit); -managerFee = totalFee.sub(protocolFee); -``` - -#### Option 3 -Manager charges a managerFee that is split with the protocol based on a feeSplit % determined by governance. Additionally, the protocol can charge a direct fee % (similar to NAVIssuanceModule). This will be a superset of TradeModule fees charged - -``` -uint256 protocolDirectFeePercent = controller.getModuleFee(address(this), _protocolDirectFeeIndex); -``` - -#### Option 4 Governance specifies a protocol fee % and a rebate split %. The split gives the manager a rebate on a percentage of protocol fees. Manager has no say and cannot rug. ``` @@ -59,7 +40,12 @@ rebateFee = protocolDirectFee.sub(protocolFee); ``` #### Recommendation -Option 4 gives us the most protection against rugging while functioning as a rebate mechanism. This means we will charge a protocol fee to start in order to activate rebates. E.g. start with a 10 bps protocol fee and 5 bps rebate +Option 4 gives us protection against rugging while providing us a base rebate mechanism. This means we will charge a protocol fee to start in order to activate rebates. E.g. start with a 10 bps protocol fee and 5 bps rebate. + +Because the rebate % is fixed by governance across all Sets, the only variable is AUM of the Set. The more user AUM vs manager AUM the more rebates managers receive (which eventually will become negative). + +#### Future work +Tiered rebates: With this base rebate mechanism, manager contracts can be built on top in the future. Individual managers will always have the ability to specify a fee recipient address as their own, but manager contracts for a specific product (e.g. social trading) can abstract away the fee recipient to a shared peripheral contract. This central trading contract tracks cumulative volume done through trading all the Sets that are linked, and perform calculations that split the rebates collected. This way, at the base module level, rebates are flat across Sets but at the manager contract level, rebates can be tiered ## Timeline - Spec + review: 2 days @@ -71,7 +57,6 @@ Option 4 gives us the most protection against rugging while functioning as a reb - Write docs: 1 day ## Checkpoint 1 -Before more in depth design of the contract flows lets make sure that all the work done to this point has been exhaustive. It should be clear what we're doing, why, and for who. All necessary information on external protocols should be gathered and potential solutions considered. At this point we should be in alignment with product on the non-technical requirements for this feature. It is up to the reviewer to determine whether we move onto the next step. **Reviewer**: @@ -82,21 +67,30 @@ Before more in depth design of the contract flows lets make sure that all the wo - Override `trade` - Add `virtual` to TradeModule V1 - Add `_accrueManagerFee` -- Override constructor to add `managerFeeRecipient` +- Override constructor to add `managerRebateRecipient` ### GeneralIndexModuleV2 - Inherit GeneralIndexModule - Override `trade` and `tradeRemainingWETH` - Add `virtual` to GeneralIndexModule trade functions - Add `_accrueManagerFee` -- Override constructor to add `managerFeeRecipient` +- Override constructor to add `managerRebateRecipient` ## Requirements - GeneralIndexModule requires no changes to the IC extension contracts except a redeployment - +- External trading interfaces stay exactly the same +- Manager fee recipient automatically receives the rebate in their wallet ## User Flows -- Highlight *each* external flow enabled by this feature. It's helpful to use diagrams (add them to the `assets` folder). Examples can be very helpful, make sure to highlight *who* is initiating this flow, *when* and *why*. A reviewer should be able to pick out what requirements are being covered by this flow. +![flow diagram](../assets/tradeModuleRebates.png) +### A manager is looking to trade 10 ETH to USDC. +1. Manager calls trade() on `TradeModule` passing in the input tokens, output tokens, path, slippage tolerance, and `SushiswapExchangeAdapter` as the exchange +2. `TradeModule` gets approval and trade calldata via `SushiswapExchangeAdapter` +3. `TradeModule` calls invoke on the SetToken, with the calldata to execute an approval and trade transaction. +4. The trade is executed on Sushiswap and USDC is returned to the SetToken and validated +5. The `TradeModuleV2` calculates the protocol fee % and sends the protocol's share to the Controller fee recipient +6. The `TradeModuleV2` calculates the rebate fee % and sends the manager's share to the manager fee recipient + ## Checkpoint 2 Before we spec out the contract(s) in depth we want to make sure that we are aligned on all the technical requirements and flows for contract interaction. Again the who, what, when, why should be clearly illuminated for each flow. It is up to the reviewer to determine whether we move onto the next step. diff --git a/assets/tradeModuleRebates.png b/assets/tradeModuleRebates.png new file mode 100644 index 0000000000000000000000000000000000000000..323353f83673167d9abb2c52ffb1a25f53fc9e35 GIT binary patch literal 31377 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ⅇo#u~+ZnY|S|T(Z zsvEkVO-IrCQA(6z(UMj-iU!eBnqfRX8i%nKp}FaYxucnWu}rCIh0oVNV%+hvg<1>R z2TYXV=4lXPU--m-Z0o&W&qK0H3OywFs6N&_tF*#mGH9(`!mYi*;VNVHlyqJ_rKvHQ zInuzl8N(lmUYN)waq04jiI>4EFFlTrq8A-tp5F8H?H1BsH|t_#Tzc<%A~M)Gc+SL) zm?URijTz;&dy594=prRwTVqjWeR(}=RH>kWcQuAu>~UR2tNiRvG;|XWcu*_F-?;ka ze-B=;Zw`hq%3R-cA)Z{j(w zkU5N6K2z1o?BE7HMc2N|{BY7_h?KLMCme`%iDRpw#zI&ocqGyF$~6`}Md-h{Z#DPs z#*Cun1&XvD4Zfp7@oVubIE6wAB*RJfp2+&khyK-j9O}%96!YU1zalBH`Q9yvnu;Zx z`QLgGu*IrrI)6D#nCT3=z{RD}E_r_|-#)X#WuiRfN||V^WFeNs!;VUyPMfzjyhi1N zrE>$13I*NyxDxFRavJS1wFQSDIOWG*57pTBTD0C*H?BxAOrN;)n-8j|V_SJ^(%#T_ z@!Lb{*zPQSvxK32*b(aVmo5>rpq96mX(Rr1nnFO=y!pa1jZW>^o0~=ypBGY%808Wh zK~2I<Gh=Twt6e|RTVMSa{wdHW5X z!9vJYY71!1j@Yy8PZeO5#l7pchl&Zh!DUhWZNcS⪙EUxF@ynhiC+>3TzmuJ}abQ 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 literal 0 HcmV?d00001 From 3ad5310ab84a7c0051b8fd3f51c9d74c15730906 Mon Sep 17 00:00:00 2001 From: Richard Liang Date: Tue, 12 Oct 2021 23:40:42 -0700 Subject: [PATCH 08/10] Add checkpoint 3 --- STIPS/STIP-006.md | 201 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 179 insertions(+), 22 deletions(-) diff --git a/STIPS/STIP-006.md b/STIPS/STIP-006.md index f8da141..a2aa31d 100644 --- a/STIPS/STIP-006.md +++ b/STIPS/STIP-006.md @@ -47,6 +47,8 @@ Because the rebate % is fixed by governance across all Sets, the only variable i #### Future work Tiered rebates: With this base rebate mechanism, manager contracts can be built on top in the future. Individual managers will always have the ability to specify a fee recipient address as their own, but manager contracts for a specific product (e.g. social trading) can abstract away the fee recipient to a shared peripheral contract. This central trading contract tracks cumulative volume done through trading all the Sets that are linked, and perform calculations that split the rebates collected. This way, at the base module level, rebates are flat across Sets but at the manager contract level, rebates can be tiered +Negotiated rebates: Updating the TradeModule to a V3 to allow each Set to have its own rebate percentage (instead of a global one for now). This could be negotiated with large managers/applications that use the protocol. + ## Timeline - Spec + review: 2 days - Implementation: 2 days @@ -64,17 +66,15 @@ Tiered rebates: With this base rebate mechanism, manager contracts can be built ### TradeModuleV2 - Inherit TradeModule -- Override `trade` -- Add `virtual` to TradeModule V1 -- Add `_accrueManagerFee` -- Override constructor to add `managerRebateRecipient` +- Override `trade` to update ComponentExchanged event +- Override `_accrueProtocolFee` +- Override initialize to add `managerRebateRecipient` ### GeneralIndexModuleV2 - Inherit GeneralIndexModule -- Override `trade` and `tradeRemainingWETH` -- Add `virtual` to GeneralIndexModule trade functions -- Add `_accrueManagerFee` -- Override constructor to add `managerRebateRecipient` +- Override `trade` and `tradeRemainingWETH` +- Override `_accrueProtocolFee` +- Override initialize to add `managerRebateRecipient` ## Requirements - GeneralIndexModule requires no changes to the IC extension contracts except a redeployment @@ -98,33 +98,190 @@ Before we spec out the contract(s) in depth we want to make sure that we are ali Reviewer: [] ## Specification -### [Contract Name] +### TradeModuleV2 +- Only changelog #### Inheritance -- List inherited contracts -#### Structs +- TradeModule +#### Constants +| Type | Name | Description | Value | +|------ |------ |------------- |------- | +|uint256|TRADE_MODULE_PROTOCOL_FEE_INDEX | Id of protocol fee % assigned to this module in the Controller | 0 | +|uint256|TRADE_MODULE_MANAGER_REBATE_SPLIT_INDEX | Id of manager rebate % assigned to this module in the Controller | 0 | +#### Public Variables | Type | Name | Description | |------ |------ |------------- | -|address|manager|Address of the manager| -|uint256|iterations|Number of times manager has called contract| +|mapping(ISetToken => address);|managerRebateRecipient|Mapping of SetToken to address of rebate recipient| +#### Functions +| Name | Caller | Description | +|------ |------ |------------- | +|initialize|Manager|Set manager rebate recipient address| +#### Functions +> initialize(SetToken _setToken, address _managerRebateRecipient) external +- managerRebateRecipient: manager rebate recipient address +```solidity +function initialize( + ISetToken _setToken, + address _managerRebateRecipient +) + external + onlyValidAndPendingSet(_setToken) + onlySetManager(_setToken, msg.sender) +{ + managerRebateRecipient = _managerRebateRecipient; + + _setToken.initializeModule(); +} +``` + +> function trade(ISetToken _setToken, string memory _exchangeName, address _sendToken, uint256 _sendQuantity, address _receiveToken, uint256 _minReceiveQuantity, bytes memory _data) +- No changes to interface +```solidity +function trade( + ISetToken _setToken, + string memory _exchangeName, + address _sendToken, + uint256 _sendQuantity, + address _receiveToken, + uint256 _minReceiveQuantity, + bytes memory _data +) + external + nonReentrant + onlyManagerAndValidSet(_setToken) +{ + ... + + (uint256 protocolFeeShare, managerRebateShare) = _accrueProtocolFee(tradeInfo, exchangedQuantity); + + ... + + emit ComponentExchanged( + _setToken, + _sendToken, + _receiveToken, + tradeInfo.exchangeAdapter, + netSendAmount, + netReceiveAmount, + protocolFeeShare, + managerRebateShare + ); +} +``` + +> function _accrueProtocolFee(TradeInfo memory _tradeInfo, uint256 _exchangedQuantity) internal returns (uint256, uint256) +- No changes to interface +```solidity +function _accrueProtocolFee(TradeInfo memory _tradeInfo, uint256 _exchangedQuantity) internal returns (uint256 protocolFeeTotal, uint256 managerRebateTotal) { + + uint256 protocolTradingFeePercentage = controller.getModuleFee(address(this), TRADE_MODULE_PROTOCOL_FEE_INDEX); + uint256 managerRebateSplitPercentage = controller.getModuleFee(address(this), TRADE_MODULE_MANAGER_REBATE_SPLIT_INDEX); + + uint256 managerRebateTotal = protocolTradingFeePercentage.preciseMul(managerRebateSplit).preciseMul(_exchangedQuantity); + uint256 protocolFeeTotal = protocolTradingFeePercentage.preciseMul(_exchangedQuantity).sub(managerRebateTotal); + payProtocolFeeFromSetToken(_tradeInfo.setToken, _tradeInfo.receiveToken, protocolFeeTotal); + + if (managerRebateShare > 0) { + _setToken.strictInvokeTransfer( + _tradeInfo.receiveToken, + managerRebateRecipient, + managerRebateTotal + ); + } +} +``` + +### GeneralIndexModuleV2 +- Only changelog +#### Inheritance +- GeneralIndexModule #### Constants | Type | Name | Description | Value | |------ |------ |------------- |------- | -|uint256|ONE | The number one| 1 | +|uint256|GENERAL_INDEX_MODULE_PROTOCOL_FEE_INDEX | Id of protocol fee % assigned to this module in the Controller | 0 | +|uint256|GENERAL_INDEX_MODULE_MANAGER_REBATE_SPLIT_INDEX | Id of manager rebate % assigned to this module in the Controller | 0 | #### Public Variables | Type | Name | Description | |------ |------ |------------- | -|uint256|hodlers|Number of holders of this token| +|mapping(ISetToken => address);|managerRebateRecipient|Mapping of SetToken to address of rebate recipient| #### Functions | Name | Caller | Description | |------ |------ |------------- | -|startRebalance|Manager|Set rebalance parameters| -|rebalance|Trader|Rebalance SetToken| -|ripcord|EOA|Recenter leverage ratio| -#### Modifiers -> onlyManager(SetToken _setToken) +|initialize|Manager|Set manager rebate recipient address| #### Functions -> issue(SetToken _setToken, uint256 quantity) external -- Pseudo code +> initialize(SetToken _setToken, address _managerRebateRecipient) external +- managerRebateRecipient: manager rebate recipient address +```solidity +function initialize( + ISetToken _setToken, + address _managerRebateRecipient +) + external + onlyValidAndPendingSet(_setToken) + onlySetManager(_setToken, msg.sender) +{ + managerRebateRecipient = _managerRebateRecipient; + + ... +} +``` + +> function trade(ISetToken _setToken, IERC20 _component, uint256 _ethQuantityLimit) +- No changes to interface +```solidity +function trade( + ISetToken _setToken, + IERC20 _component, + uint256 _ethQuantityLimit +) + external + nonReentrant + onlyAllowedTrader(_setToken) + onlyEOAIfUnrestricted(_setToken) + virtual +{ + ... + + (uint256 protocolFeeShare, managerRebateShare) = _accrueProtocolFee(tradeInfo, exchangedQuantity); + + ... + + emit TradeExecuted( + tradeInfo.setToken, + tradeInfo.sendToken, + tradeInfo.receiveToken, + tradeInfo.exchangeAdapter, + msg.sender, + netSendAmount, + netReceiveAmount, + protocolFeeShare, + managerRebateShare + ); +} +``` + +> function _accrueProtocolFee(TradeInfo memory _tradeInfo) internal returns (uint256, uint256) +- No changes to interface +```solidity +function _accrueProtocolFee(TradeInfo memory _tradeInfo) internal returns (uint256 protocolFeeTotal, uint256 managerRebateTotal) { + ... + + uint256 protocolTradingFeePercentage = controller.getModuleFee(address(this), TRADE_MODULE_PROTOCOL_FEE_INDEX); + uint256 managerRebateSplitPercentage = controller.getModuleFee(address(this), TRADE_MODULE_MANAGER_REBATE_SPLIT_INDEX); + + uint256 managerRebateTotal = protocolTradingFeePercentage.preciseMul(managerRebateSplit).preciseMul(_exchangedQuantity); + uint256 protocolFeeTotal = protocolTradingFeePercentage.preciseMul(_exchangedQuantity).sub(managerRebateTotal); + payProtocolFeeFromSetToken(_tradeInfo.setToken, _tradeInfo.receiveToken, protocolFeeTotal); + + if (managerRebateShare > 0) { + _setToken.strictInvokeTransfer( + _tradeInfo.receiveToken, + managerRebateRecipient, + managerRebateTotal + ); + } +} +``` + ## Checkpoint 3 Before we move onto the implementation phase we want to make sure that we are aligned on the spec. All contracts should be specced out, their state and external function signatures should be defined. For more complex contracts, internal function definition is preferred in order to align on proper abstractions. Reviewer should take care to make sure that all stake holders (product, app engineering) have their needs met in this stage. From 18e0a574cdba8dd3d85908ab3480150bcaa370dd Mon Sep 17 00:00:00 2001 From: Richard Liang Date: Tue, 12 Oct 2021 23:42:12 -0700 Subject: [PATCH 09/10] Fix typo --- STIPS/STIP-006.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/STIPS/STIP-006.md b/STIPS/STIP-006.md index a2aa31d..2b084d2 100644 --- a/STIPS/STIP-006.md +++ b/STIPS/STIP-006.md @@ -106,7 +106,7 @@ Reviewer: [] | Type | Name | Description | Value | |------ |------ |------------- |------- | |uint256|TRADE_MODULE_PROTOCOL_FEE_INDEX | Id of protocol fee % assigned to this module in the Controller | 0 | -|uint256|TRADE_MODULE_MANAGER_REBATE_SPLIT_INDEX | Id of manager rebate % assigned to this module in the Controller | 0 | +|uint256|TRADE_MODULE_MANAGER_REBATE_SPLIT_INDEX | Id of manager rebate % assigned to this module in the Controller | 1 | #### Public Variables | Type | Name | Description | |------ |------ |------------- | @@ -198,7 +198,7 @@ function _accrueProtocolFee(TradeInfo memory _tradeInfo, uint256 _exchangedQuant | Type | Name | Description | Value | |------ |------ |------------- |------- | |uint256|GENERAL_INDEX_MODULE_PROTOCOL_FEE_INDEX | Id of protocol fee % assigned to this module in the Controller | 0 | -|uint256|GENERAL_INDEX_MODULE_MANAGER_REBATE_SPLIT_INDEX | Id of manager rebate % assigned to this module in the Controller | 0 | +|uint256|GENERAL_INDEX_MODULE_MANAGER_REBATE_SPLIT_INDEX | Id of manager rebate % assigned to this module in the Controller | 1 | #### Public Variables | Type | Name | Description | |------ |------ |------------- | From 1cf9bfac0b9fbd2a6b5aa5748b55a9c0e493dcd0 Mon Sep 17 00:00:00 2001 From: Richard Liang Date: Thu, 14 Oct 2021 09:49:15 -0700 Subject: [PATCH 10/10] Address checkpoint 3 comments --- STIPS/STIP-006.md | 54 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 41 insertions(+), 13 deletions(-) diff --git a/STIPS/STIP-006.md b/STIPS/STIP-006.md index 2b084d2..a57acc8 100644 --- a/STIPS/STIP-006.md +++ b/STIPS/STIP-006.md @@ -65,19 +65,21 @@ Negotiated rebates: Updating the TradeModule to a V3 to allow each Set to have i ## Proposed Architecture Changes ### TradeModuleV2 -- Inherit TradeModule -- Override `trade` to update ComponentExchanged event -- Override `_accrueProtocolFee` -- Override initialize to add `managerRebateRecipient` +- Copy TradeModule into new file +- Update ComponentExchanged event +- Update `_accrueProtocolFee` to include rebate logic +- Add `managerRebateRecipient` on initialize ### GeneralIndexModuleV2 - Inherit GeneralIndexModule - Override `trade` and `tradeRemainingWETH` -- Override `_accrueProtocolFee` -- Override initialize to add `managerRebateRecipient` +- Update `_accrueProtocolFee` to include rebate logic +- Add `managerRebateRecipient` on initialize +- Change GIM extension interface ## Requirements -- GeneralIndexModule requires no changes to the IC extension contracts except a redeployment +- Update GIMExtension interface in IC repo +- UI and backend must update to initialize flow with manager address - External trading interfaces stay exactly the same - Manager fee recipient automatically receives the rebate in their wallet @@ -133,6 +135,32 @@ function initialize( } ``` +> updateManagerRebateRecipient(SetToken _setToken, address _newFeeRecipient) external +- setToken: Address of SetToken +- newFeeRecipient: manager rebate recipient address +```solidity +/** + * MANAGER ONLY: Updates address receiving issue/redeem fees for a given SetToken. + * + * @param _setToken Instance of the SetToken to update fee recipient + * @param _newFeeRecipient New fee recipient address + */ +function updateFeeRecipient( + ISetToken _setToken, + address _newFeeRecipient +) + external + onlyManagerAndValidSet(_setToken) +{ + require(_newFeeRecipient != address(0), "Fee Recipient must be non-zero address."); + require(_newFeeRecipient != managerRebateRecipient[_setToken], "Same fee recipient passed"); + + managerRebateRecipient[_setToken].feeRecipient = _newFeeRecipient; + + emit FeeRecipientUpdated(_setToken, _newFeeRecipient); +} +``` + > function trade(ISetToken _setToken, string memory _exchangeName, address _sendToken, uint256 _sendQuantity, address _receiveToken, uint256 _minReceiveQuantity, bytes memory _data) - No changes to interface ```solidity @@ -151,7 +179,7 @@ function trade( { ... - (uint256 protocolFeeShare, managerRebateShare) = _accrueProtocolFee(tradeInfo, exchangedQuantity); + (uint256 protocolFeeShare, managerRebateShare) = _accrueRebateAndProtocolFees(tradeInfo, exchangedQuantity); ... @@ -168,10 +196,10 @@ function trade( } ``` -> function _accrueProtocolFee(TradeInfo memory _tradeInfo, uint256 _exchangedQuantity) internal returns (uint256, uint256) +> function _accrueRebateAndProtocolFees(TradeInfo memory _tradeInfo, uint256 _exchangedQuantity) internal returns (uint256, uint256) - No changes to interface ```solidity -function _accrueProtocolFee(TradeInfo memory _tradeInfo, uint256 _exchangedQuantity) internal returns (uint256 protocolFeeTotal, uint256 managerRebateTotal) { +function _accrueRebateAndProtocolFees(TradeInfo memory _tradeInfo, uint256 _exchangedQuantity) internal returns (uint256 protocolFeeTotal, uint256 managerRebateTotal) { uint256 protocolTradingFeePercentage = controller.getModuleFee(address(this), TRADE_MODULE_PROTOCOL_FEE_INDEX); uint256 managerRebateSplitPercentage = controller.getModuleFee(address(this), TRADE_MODULE_MANAGER_REBATE_SPLIT_INDEX); @@ -241,7 +269,7 @@ function trade( { ... - (uint256 protocolFeeShare, managerRebateShare) = _accrueProtocolFee(tradeInfo, exchangedQuantity); + (uint256 protocolFeeShare, managerRebateShare) = _accrueRebateAndProtocolFees(tradeInfo, exchangedQuantity); ... @@ -259,10 +287,10 @@ function trade( } ``` -> function _accrueProtocolFee(TradeInfo memory _tradeInfo) internal returns (uint256, uint256) +> function _accrueRebateAndProtocolFees(TradeInfo memory _tradeInfo) internal returns (uint256, uint256) - No changes to interface ```solidity -function _accrueProtocolFee(TradeInfo memory _tradeInfo) internal returns (uint256 protocolFeeTotal, uint256 managerRebateTotal) { +function _accrueRebateAndProtocolFees(TradeInfo memory _tradeInfo) internal returns (uint256 protocolFeeTotal, uint256 managerRebateTotal) { ... uint256 protocolTradingFeePercentage = controller.getModuleFee(address(this), TRADE_MODULE_PROTOCOL_FEE_INDEX);