Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Authorizations #358

Closed
wants to merge 47 commits into from
Closed

Conversation

nicolaslara
Copy link

This PR should replace #305 .

The approach here is much simpler with the authorizations integrated into the proposals. We can discuss if this is the right approach, but the middleware approach added so much complexity that I don't think it's worth going that route.

The most interesting part of this PR should be the message-filter contract implementation and its tests. Additionally the proposal tests should give an idea of how this would be used in a more realistic scenario.

The PR is not production-ready, but I'd like to get some feedback before moving forward with the implementation.

@JakeHartnell JakeHartnell requested a review from 0xekez June 16, 2022 09:46
// * Write proper tests for the auth, and more realistic tests for this proposal where we don't fake the sender
//
let response = Response::default();
let response = if let Some(auths) = AUTHORIZATION_MODULE.may_load(deps.storage)? {
Copy link
Contributor

Choose a reason for hiding this comment

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

are the performance implications of adding this query to every message negligible?

Copy link
Author

Choose a reason for hiding this comment

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

The performance implications here are:

  • Loading the addr to the authorization module
  • Calling the querier
  • The query loads any sub-authorizations and queries those

In the best case scenario, this has very low overhead. If you have many authorizations configured, though, it could be considerable if the authorization contracts are complex. However, I'd argue that if you've added authorizations here, then that's exactly the behaviour you want: the authorizations need to be checked to know if the message is allowed or not.

That said, I'm not sure how useful it is to be able to add auths to proposal-single. I added that cause it was the easiest way to test initially, but then figured out that making the auth manager behave like a proposal allows for more flexibility.

Right now, the authorizations contract can serve as a proposal, so you can add that contract to the dao as a proposal manager and any proposals that require auths can go through it while you can still have proposal-single for voting proposals. I still have to clean some of this up and add tests, but would love your opinion on whether it makes sense to have auths as part of proposal-single or not.

@nicolaslara
Copy link
Author

nicolaslara commented Aug 1, 2022

This is now "feature complete". It currently supports:

  • Ability to restrict the proposals that can be executed via proposal-single (based on the sender and the messages)
  • Adding a cw-auth-middleware contract to cw-core as a proposal. This means we can call Execute on cw-auth-middleware and, if the auth passes, have the dao execute the messages in the same block (without the need for voting)
  • Adding cw-4 groups to cw-auth-middleware so that any member of the group shares the same permissions
  • Three authorization contracts:
    • message-filter: The most powerful auth, that accepts a custom message matching pattern and allows or rejects any message that matches the pattern
    • and: Authorizes a message if all its children authorize it
    • whitelist: Authorizes any message by a whitelisted sender

There are a lot of changes and improvements that I would like to improve here, but I think I should focus on the minimal cleanup needed to be able to ship this and keep adding the improvements in future versions.

For reference, these are some of the things I would like to improve (in no particular order):

  • Rename cw-auth-middlware to cw-auth-manager. It's not a middleware!
  • Document and define the Authorization spec
  • Simplify code reuse. This could be done by:
    • Implementing an Authorization trait that contains default implementations
    • Either reorganize the code so the contract one object (like ng-contracts or Sylvia (https://github.com/CosmWasm/sylvia))
    • Or by adding generic functions that can be imported by the different auth implementation
    • Messages in the auth spec may need to be derived in sub auth contracts
  • Split group management into its own contract. This would simplify the implementation of the auth manager.
  • Add logical auth contracts for composition (any, all, not). This simplifies the construction of complex auths and makes the auth manager cleaner
  • Make it easy to create complex auths via nested instantiate messages (similar to cw-core)
  • Allow the auth manager to specify the default Allow/Reject behaviour (similar to message filter)
  • Tooling that makes it easy to use auths. Can be done via:
    • Proxy-accounts for keplr
    • JS code that wraps a transaction in the necessary auth boilerplate so that apps can implement the calls themselves
  • Document everything

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult<Binary> {
match msg {
QueryMsg::Authorize { msgs, sender } => authorize_messages(deps, env, msgs, sender),
Copy link
Member

Choose a reason for hiding this comment

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

Rename to QueryMsg::IsAuthorized?

authorize_messages sounds like an execute method when it's actually a query handler.

Copy link

@ethanfrey ethanfrey left a comment

Choose a reason for hiding this comment

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

This needs more design discussion on a few levels

Number(u64), // Why doesn't u128 work here?
String(String),
Array(Vec<Value>),
Object(Map<String, Value>),

Choose a reason for hiding this comment

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

I am quite worried about using Map in any serde serialisable struct.
Also, very odd to see this come from schemars.

This may be safe, but needs some very careful checking.
Minimally, check the json encoding "manually" in test cases.
Also ensure that when this compiles to wasm, it passes check_contract
(see this example)

Copy link
Author

Choose a reason for hiding this comment

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

Ran the checks and they seem to pass. I can run them on the optimized contract later.
__devel_dao-contracts

I've tested this manually and it works on the chain, and schemars uses https://docs.rs/indexmap/latest/indexmap/ internally to preserve ordering. I can add some more tests to the serialization/deserialization specifically focusing on ordering (is non-determinism your main concern?)

})
}

#[cfg_attr(not(feature = "library"), entry_point)]

Choose a reason for hiding this comment

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

I don't think this need to be a contract but a library imported directly by thing using it.

Copy link
Author

Choose a reason for hiding this comment

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

It makes sense to refactor the message parsing and matching into a library. We would still need a message filter contract if we want DAOs to add auths in a generic way (for example: authorize an account to mint NFTs from the DAO account to give to members)

@@ -0,0 +1,54 @@
[package]
name = "and"
Copy link
Member

Choose a reason for hiding this comment

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

Can we think of a better name for this contract? 😂

Copy link
Author

Choose a reason for hiding this comment

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

logical-and? Thought maybe it should be something auth specific, like must-pass-all or authorization-joiner-and 🤷 open to suggestions

Comment on lines +275 to +277
// Check that the proposal is authorized if authorizations are configured
let response = Response::default();
let response = if let Some(auths) = AUTHORIZATION_MODULE.may_load(deps.storage)? {
Copy link
Member

Choose a reason for hiding this comment

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

That said, I'm not sure how useful it is to be able to add auths to proposal-single. I added that cause it was the easiest way to test initially, but then figured out that making the auth manager behave like a proposal allows for more flexibility.

Ok, getting caught up on this... I think allowing the auth manager to behave like a proposal would be best.

Here's what I envision:

SubDAO -------> AuthorizationsContract --------> DAO

So the parent DAO would add the Authorizations module as a proposal module. The authorizations contract would then be able to call ExecuteProposalHook on the main DAO. The SubDAO would simply vote to call the authorizations contract with the message they intend to execute.

Copy link
Author

Choose a reason for hiding this comment

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

Yeah, I like this approach too.

Just to be clear, this is currently possible (see the tests on cw-auth-middleware), the questions is if this should be the only way of doing it or we should keep the changes to proposal single as well.

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
match msg {
QueryMsg::Authorize { msgs, sender } => query_authorizations(deps, msgs, sender),
Copy link
Member

Choose a reason for hiding this comment

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

Maybe rename to IsAuthorized?

@JakeHartnell
Copy link
Member

This is now "feature complete". It currently supports:

  • Ability to restrict the proposals that can be executed via proposal-single (based on the sender and the messages)

  • Adding a cw-auth-middleware contract to cw-core as a proposal. This means we can call Execute on cw-auth-middleware and, if the auth passes, have the dao execute the messages in the same block (without the need for voting)

  • Adding cw-4 groups to cw-auth-middleware so that any member of the group shares the same permissions

  • Three authorization contracts:

    • message-filter: The most powerful auth, that accepts a custom message matching pattern and allows or rejects any message that matches the pattern
    • and: Authorizes a message if all its children authorize it
    • whitelist: Authorizes any message by a whitelisted sender

Wow, this is a lot! Very powerful, I like the general direction.

Less might be more here as we will need to get these contracts documented and audited.

Would be nice to simplify things a bit. Think MVP. What things can we remove? IMO the main contracts are the cw-auth-middleware and message-filter.

  • Split group management into its own contract. This would simplify the implementation of the auth manager.

I would argue groups should not be part of cw-auth-middleware because we can achieve the same functionality using cw1-whitelist! So it's already done! cw1-whitelist can be managed by a group or a DAO.

  • Implementing an Authorization trait that contains default implementations

This is a great idea!

  • Messages in the auth spec may need to be derived in sub auth contracts

"Sub Auth contracts seem like something we could put off for a V2.

  • Add logical auth contracts for composition (any, all, not). This simplifies the construction of complex auths and makes the auth manager cleaner
  • Make it easy to create complex auths via nested instantiate messages (similar to cw-core)
  • Allow the auth manager to specify the default Allow/Reject behaviour (similar to message filter)

More things that I would suggest should be out of scope until a V2. Let's build something simple first and do the hard stuff like message filtering well and then think about about how such things can be composed.

  • Tooling that makes it easy to use auths.

Integrating proposal templates and UI for this would be a great start... but again MVP!

This is a really great start though. Happy to setup a call to talk through this stuff. I was initially skeptical about implementing message filter as a separate contract, but I think I'm convinced now.

@nicolaslara
Copy link
Author

Thanks for the feedback @JakeHartnell! I'm a 100% onboard with simplifying this.

I would argue groups should not be part of cw-auth-middleware because we can achieve the same functionality using cw1-whitelist! So it's already done! cw1-whitelist can be managed by a group or a DAO.

I agree they should not be part of cw-auth-middleware. It's also one of the main sources of complexity, so removing it would help.

The only issue I see with using cw1-whitelist for group auth, is that you have to interact the dao in a different way if you're the one authorized vs if the cw1-whitelist contract is the one authorized, but that may be a tradeoff that is worth it for the simplicity gains.

Let's discuss this on a call and make a plan for shipping it!

@JakeHartnell
Copy link
Member

The only issue I see with using cw1-whitelist for group auth, is that you have to interact the dao in a different way if you're the one authorized vs if the cw1-whitelist contract is the one authorized, but that may be a tradeoff that is worth it for the simplicity gains.

Worth the tradeoff! Getting people to use it will inform next steps. Simple is good.

Let's discuss this on a call and make a plan for shipping it!
Let's do it!

@nicolaslara
Copy link
Author

Started some of the work of removing bloat and generalizing auths as an interface here: https://github.com/nicolaslara/dao-contracts/tree/authorizations_as_interface. Still a WIP, but it should already be closer to an MVP. I want to cleanup the interface and document how to use it. Maybe the easiest way is to just create a template for a minimal auth that can be created via cargo generate to avoid adding more complexity to the Trait

@nicolaslara
Copy link
Author

another short update here: I've been trying to move everything that is not specific to daodao to its own repo: https://github.com/nicolaslara/authorizations. Most of it is there and the interface is relatively clean/stable. Now I just need to modify the daodao parts to use that interface. I'll try to have this ready by cosmoverse

@nicolaslara
Copy link
Author

a much simple implementation of this is now at https://github.com/DA0-DA0/dao-contracts/compare/main...nicolaslara:dao-contracts:nico/authorizations?expand=1

This is now only an Authorizations based proposal for dao dao, all the implementations of auths have been moved to a different repo.

@JakeHartnell @ezekiiel we should discuss this at cosmoverse

@0xekez
Copy link
Contributor

0xekez commented Nov 17, 2022

i'm going to close this for now as i feel our focus is elsewhere. the work here is very good. lets reopen this when we return to authorizations and have some more coherency about what our design requirements are.

@0xekez 0xekez closed this Nov 17, 2022
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.

None yet

6 participants