Skip to content

Conversation

cgewecke
Copy link
Contributor

@cgewecke cgewecke commented Aug 13, 2021

PR implements

Summary

  • Makes fee, fee split and fee recipient updates mutualUpgrade in existing fee extension contracts
  • Makes fee extension address the default feeRecipient to firewall fees from other extensions.
  • Adds methods to guarantee that operator cannot unilaterally change fee arrangements.

New Permissions

The permissions model proposed here has the following properties:

  • supports a registry of protected modules and defines which extensions may call them
  • protected modules are configured after deployment before the contract is enabled
  • the methodologist must enable the manager contract before it can begin operation
  • updates to protected module arrangements can only be performed by mutual upgrade, except under emergency conditions
  • protected modules cannot be unilaterally removed, except under emergency circumstances
  • extensions authorized for protected modules cannot be unilaterally removed, except under emergency conditions
  • the operator can protect new modules at their discretion, on behalf of the methodologist
  • the methodologist can un-protect modules at their discretion, on behalf of the operator

TODO

  • Debug remaining skipped tests
  • Events tests
  • Update spec with new getters
  • Add method to allow operator to set an address which will receive its fees.
  • Resolve accrual protection design questions
  • Format as new manager contract, renaming adapter as extension.
  • Fixes for second review sweep

Open Questions

Have retained the "adapter" naming convention to reduce noise in the diff. This PR might be the right time to rename everything extension?

There's an asymmetry in the preconditions for replacing modules vs. protecting them.

  • Replacing a module adds and removes from the SetToken, meaning the replacement cannot already be added
  • Protecting a module (operator only) requires that the module already be added.

The idea behind this is that in the "replacement" scenario, we don't really want the operator to double-up fee modules - it's cleaner to model as an atomic swap. In the "protect" scenario, the operator is adding new features and should have more flexibility. Does this make sense? Or should behavior in these two scenarios be standardized?

@cgewecke cgewecke marked this pull request as ready for review August 13, 2021 14:27
@cgewecke cgewecke changed the title [WIP] Add Manager permissions for protected modules and extensions Add Manager permissions for protected modules and extensions Aug 13, 2021
Copy link
Contributor

@bweick bweick left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple logic things I think we wanna clean up and then let's try to xfer everything to Extension terminology at this point, have a feeling we'll be using this new manager going forward (hence wanna have it in a new file).

One note that may be good to add is that Extensions can be added through:
addAdapter (Extension)
protectModule
replaceProtectedModule
emergencyReplaceProtectedModule
authorizeAdapter (Extension...and if we make the logic update I proposed)

Extensions can be removed (from adapters array):
removeAdapter (Extension)

Extensions can be authorized:
protectModule
replaceProtectedModule
emergencyReplaceProtectedModule
authorizeAdapter (Extension...and if we make the logic update I proposed)

Extensions can be deauthorized:
replaceProtectedModule
emergencyRemoveProtectedModule
unProtectModule

Want to make sure that's your understanding as well. Think it's important to have those mapped out, that's the part I found the most confusing about it is the different Extension states.



/**
* @title FeeSplitAdapter
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah let's take this opportunity to rename the all the leftover "adapters" to "extensions"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These renamings done from ea81801 to cbb6b7c

streamingFeeModule.accrueFee(setToken);

uint256 totalFees = setToken.balanceOf(address(manager));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To ensure that all fees can't be poached we actually want to send fees to this extension now. So we should be reading the balance on this contract

Copy link
Contributor Author

@cgewecke cgewecke Aug 17, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is in feaf6a2.

This approach requires that any time a fee extension contract is added or replaced the fee recipient is updated (by mutual upgrade) as well.

Have added a distribute method to the extensions that lets distribution happen whether the extension is authorized or not. This might be necessary if accrue is called on the streaming fee module during a transition from one extension to another, or fees went undistributed for other reasons.

[EDIT - distribute is unnecessary because there's no routing through the manager. Have added a behavioral test that makes sure we can sweep accrued fees from a de-authorized and removed extension since this is something that might be necessary]

*/

pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I should have clarified this...I think there's enough changes here that it warrants a different manager contract. I want to preserve the old contract so maybe we make this PermissionedManager or something to that affect. There's probably a better name...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also given we're trying to move towards the Extension nomenclature and we're changing a lot about this BaseManager can we change the adapter to extension. I should have specified that in the spec, that's my bad.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I overlooked the suggestion to rename the manager descriptively and did V2.....

* OPERATOR ONLY: Marks a currently protected module as unprotected and deletes its authorized
* adapter registries. Removes module from the SetToken. Increments the `emergencies` counter,
* prohibiting any operator-only module or extension additions until `emergencyReplaceProtectedModule`
* is executed.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ro resolveEmergency is called by the methodologist

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

protectedModules[_module].authorizedAdapters[adapter] = false;
}

delete protectedModules[_module].authorizedAdaptersList;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to delete this if we're deleting protectedModules[_module]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, this is unnecessary. Removed in 7bdb9c9.

(Aside, this case is tested here)

* @param _module Module to remove
*/
function removeModule(address _module) external onlyOperator {
require(!protectedModules[_module].isProtected, "Module protected");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a note that any extensions associated with the module need to be removed in a separate transaction

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

* @param _module Module to protect
* @param _adapters Array of adapters to authorize for protected module
*/
function protectModule(address _module, address[] memory _adapters)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like there's an inconsistency in extension authorization between this and authorizeAdapter. Here there's no check to see if the extension has already been added (not authorized!) whereas the authorizeAdapter there's an explicit check to see if it has been added:

See Line 259

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would tend towards not requiring the extension to have been "added" before being "authorized"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As noted above - authorizeAdapter and protectModule now both automatically add the adapter if missing with ebd8676


/**
* MUTUAL UPGRADE**: Revokes adapter authorization for a protected module. Operator and Methodologist
* must each call this function to execute the update.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would note here that in order to remove the extension completely from the contract removeAdapter must be called by the operator

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cgewecke cgewecke force-pushed the chris/fee-permissions branch from 63062bd to feaf6a2 Compare August 17, 2021 15:44
@cgewecke cgewecke force-pushed the chris/fee-permissions branch from 5c16b6a to cbe93be Compare August 17, 2021 18:29
@cgewecke cgewecke force-pushed the chris/fee-permissions branch from e151766 to cbb6b7c Compare August 17, 2021 21:40
Copy link
Contributor

@bweick bweick left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple small things but looks really good to go to me. The tests especially are amazing, super well done and thorough. Those tests help inspire confidence.

* ANYONE CALLABLE: Accrues fees from streaming fee module. Gets resulting balance after fee accrual, calculates fees for
* operator and methodologist, and sends to each. NOTE: mint/redeem fees will automatically be sent to this address so reading
* the balance of the SetToken in the contract after accrual is sufficient for accounting for all collected fees.
* operator and methodologist, and sends to operator and methodologist, and sends to operatorFeeRecipient and methodlogist
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant statement here. Also methodologist

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


event FeesAccrued(address indexed _operator, address indexed _methodologist, uint256 _operatorTake, uint256 _methodologistTake);

event FeesDistributed(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't know if IC analytics is looking for the FeesAccrued event but should let them know this is changed if they are. Think it's definitely better naming

/**
* ONLY OPERATOR: Updates streaming fee on StreamingFeeModule. NOTE: This will accrue streaming fees though not send to operator
* and methodologist.
* MUTUAL UPGRADE: Updates streaming fee on StreamingFeeModule. NOTE: This will accrue streaming
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should make a note for all of these mutualUpgrade and timeLockUpgrade combo functions that it will require each party submitting two txns. One to set the timelock and the next to execute the function

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

require(isAdapter[_adapter], "Adapter does not exist");
require(!protectedModules[_module].authorizedAdapters[_adapter], "Adapter already authorized");

if (!isAdapter[_adapter]) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bit of a nit: But we can maybe DRY this up a bit more by making an internal function that does the state updates. That can be used here and in protectModule.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

subjectProtectedModules = [subjectModule];
});

// This test is borked... module initialized in before block..
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's wrong with this test?

Copy link
Contributor Author

@cgewecke cgewecke Aug 18, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoah! Glad you flagged this. The constructor does not add modules to the SetToken. I don't this this is possible - the setToken.addModule call reverts with Manager only because the manager contract doesn't technically exist yet.

So ... this situation is a little weird. You could protect a module in the Manager deployment which isn't on the token.

Have removed the bad test and replaced with one where non-added module is protected in the constructor and operator adds module after deployment.

62c226a

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think this is ok

});
});

// This test for the coverage report - hits an alternate branch condition the authorized
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good to note

@@ -0,0 +1,1710 @@
import "module-alias/register";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests are amazing and so thorough. Well laid out and even checking to make sure all critical pieces of state remain unchanged within different flows. This is chef's kiss.


_protectModule(_newModule, _newExtensions);

ReplacedProtectedModule(_oldModule, _newModule, _newExtensions);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we not need the emit here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apparently it just works. However added the missing ones for consistency's sake in 87c8fcf

@cgewecke
Copy link
Contributor Author

Have added some new changes to address bugs / issues discovered this morning:

  • The constructor logic that protects modules had a circular dependency with the Manager. Have removed that and added a test for the flow where:
    • manager is deployed
    • modules are protected as necessary
    • methodologist reviews the config and calls authorizeInitialization.

The replaceProtectedModule -type methods add modules to the SetToken. These need to be initialized via the manager before they can be used. initialize methods for the relevant modules are added to the extensions in

@cgewecke cgewecke merged commit 17909eb into master Aug 19, 2021
@cgewecke cgewecke deleted the chris/fee-permissions branch August 19, 2021 00:04
ace3 pushed a commit to ace3/index-coop-smart-contracts that referenced this pull request Feb 2, 2024
Updated javadocs for GeneralIndexModule.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants