-
Notifications
You must be signed in to change notification settings - Fork 39
feat: use proposed contracttrait type
#350
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
base: main
Are you sure you want to change the base?
Conversation
✅ Deploy Preview for delightful-dieffenbachia-2097e0 canceled.
|
746fa43 to
56f0362
Compare
5d8bb5a to
2869014
Compare
2869014 to
9bc9170
Compare
|
I'll post some of the discussion points below that happened through DM. So, that @brozorec and others can chime in. |
| type ContractType = AllowList; | ||
| type Impl = AllowList; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Me:
ContractTypeis changed toImpl. We decided to keep it asContractTypein our previous discussion
Willem:
My branch of the contracttraits adds the default impl redirect, but uses Impl . What I wanted was to be consistent with contracttraits which used the associated type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still think ContractType is more self-explanatory than an abstract name such as Impl. I'd prefer to keep it as ContractType
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about ContractImpl? Type is also broad and it already is a type. What it is is the implementation of the contract so ContractImpl seems appropriate to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it is already a type
Yeah I give you that :D
I don't think Impl keyword fits in here.
What it is is the implementation of the contract so ContractImpl seems appropriate to me.
I disagree. It is the associated type. The implementation of the contract is happening elsewhere, this is just the indicator type. I can go with Contract alone, agree that Type is redundant, but I don't think Impl fits here.
|
|
||
| #[contractimpl] | ||
| impl FungibleBurnable for ExampleContract { | ||
| type Impl = AllowList; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Me:
Why have we added
ContractTypeto every trait? It was not required back then forFungibleAllowList,FungibleBurnable,AccessControl, etc.
Willem:
The idea was that I wanted an actual default implementation for each. This way devs could replace it with their own and opens up to the trait extensions which I use an another branch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are specific reasons that we didn't want to provide default implementations for some modules, like AccessControl. This was a deliberate choice because the functions that will be implemented for the contract are critical w.r.t security, and providing defaults will effectively result in people using the default rather than carefully thinking about their business logic and how to configure AccessControl accordingly.
The reason for the existence of the ContractType trait, is to provide overrides across different types of contracts. For Allowlist, Burnable, AccessControl, there are no overrides needed for their interface.
I'd say this change is contradicting with 2 critical design choices of our library.
|
|
||
| // Mint initial supply to the admin | ||
| Base::mint(e, &admin, initial_supply); | ||
| Self::internal_mint(e, admin, initial_supply); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Me:
internal_mintis a new addition?
Willem:
mintwas public but added aninternal_mintto theFTtrait with#[internal]. This way if you you wanted to swap of the default implementation it would be guaranteed to have it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I fully get it, could you elaborate on that a bit more @willemneal ?
| let (receiver, amount) = client.get_royalty_info(&token_id, &1000); | ||
| let (receiver, amount) = client.royalty_info(&token_id, &1000); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Me:
get_royalty_infois renamed toroyalty_info, debatable…
Willem:
It wasn’t renamed because
royalty_infowas part of the trait and thus exposed already. So there were two functions that did the same thing. So I’m open to renaming.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, that sounds good! This is not a rename for the storage or interface, but a rename to the specific example contract implementation.
| /// * topics - `["disallow", user: Address]` | ||
| /// * data - `[]` | ||
| fn disallow_user(e: &Env, user: Address, operator: Address); | ||
| fn disallow_user(e: &Env, user: &soroban_sdk::Address, operator: &soroban_sdk::Address); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why are we using the full path here &soroban_sdk::Address? Address is already imported above from soroban_sdk?
| pub trait NonFungibleEnumerable: NonFungibleToken<ContractType = Enumerable> { | ||
| #[contracttrait(add_impl_type = true)] | ||
| pub trait NonFungibleEnumerable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does it still work in mutually exclusive way with Consecutive for example?
What I mean is, trait bound is removed: pub trait NonFungibleEnumerable: NonFungibleToken<ContractType = Enumerable>
and, this trait bound was allowing us to get mutual exclusivity across incompatible contract types. More concretely, one cannot implement both Enumerable and Consecutive for the same contract.
brozorec
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@willemneal great work 👌 This approach is viable and I think it makes the library more ergonomic.
There is the question about the unimplemented function (cf. one of the comments) that's a blocker for me atm.
| /// * topics - `["role_granted", role: Symbol, account: Address]` | ||
| /// * data - `[caller: Address]` | ||
| fn grant_role(e: &Env, caller: Address, account: Address, role: Symbol); | ||
| fn grant_role(e: &Env, caller: &Address, account: &Address, role: &soroban_sdk::Symbol); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why some of the params are fully-qualified and others are not? What's the benefit?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The contracttrait macro will generate the code and this basically means copy/pasting the types. So without this it means you will need to import them. Which isn't too bad but could be confusing for devs.
| fn enforce_admin_auth(e: &Env) { | ||
| let Some(admin) = Self::get_admin(e) else { | ||
| soroban_sdk::panic_with_error!(e, AccessControlError::AdminNotSet); | ||
| }; | ||
| admin.require_auth(); | ||
| } | ||
|
|
||
| fn ensure_role(e: &Env, caller: &soroban_sdk::Address, role: &soroban_sdk::Symbol) { | ||
| if Self::has_role(e, caller, role).is_none() { | ||
| soroban_sdk::panic_with_error!(e, AccessControlError::Unauthorized); | ||
| } | ||
| } | ||
|
|
||
| fn ensure_if_admin_or_admin_role( | ||
| e: &Env, | ||
| caller: &soroban_sdk::Address, | ||
| role: &soroban_sdk::Symbol, | ||
| ) { | ||
| let is_admin = match Self::get_admin(e) { | ||
| Some(admin) => caller == &admin, | ||
| None => false, | ||
| }; | ||
| let is_admin_role = match Self::get_role_admin(e, role) { | ||
| Some(admin_role) => Self::has_role(e, caller, &admin_role).is_some(), | ||
| None => false, | ||
| }; | ||
| if !is_admin && !is_admin_role { | ||
| soroban_sdk::panic_with_error!(e, AccessControlError::Unauthorized); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If there are default implementations for those internals, why do we re-impl them here?
| #[internal] | ||
| fn only_owner(e: &soroban_sdk::Env) { | ||
| let Some(owner) = Self::get_owner(e) else { | ||
| panic_with_error!(e, OwnableError::OwnerNotSet); | ||
| }; | ||
| owner.require_auth() | ||
| } | ||
|
|
||
| #[internal] | ||
| fn enforce_owner_auth(e: &soroban_sdk::Env) -> Address { | ||
| let Some(owner) = Self::get_owner(e) else { | ||
| panic_with_error!(e, OwnableError::OwnerNotSet); | ||
| }; | ||
| owner.require_auth(); | ||
| owner | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe we don't need them both
| fn allow_user(e: &Env, user: &Address, _operator: &Address) { | ||
| Self::allow_user_no_auth(e, user); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I commented out this fn and also the fn in the fungible-allowlist example + the FungibleAllowList trait in mod.rs doesn't provide a default impl, but everything still compiles 🤔
I was expecting it would scream about the unimplemented function, but that's not the case, which can be a serious issue for sensitive functions like this one.
This also simplifies Upgrade to depend on Ownable.
A continuation of #334