I was the defender of this long time, but now I have some doubts to be honest. Especially when conflict occurs on the interface.
Repeating my comment from GitHub ( which is a wonderful place actually for FLIP discussions btw ), I think this is changing the trust model from interface - contract ( one on one ) to a web of trust, (one to many )
Now I have to blindly trust all the interfaces the interface I am using trusted. As we lack versioning of contracts I think this is a big problem with compatibility and security hole. ( Imagine npm with always latest version of the packages )
We need to somehow flatten the model, but I donât know how to do that. @flowjosh 's suggestion seems really good in my opinion. But I still have doubt on that, what will happen if Vault decides to add Provider
interface such as : pub resource interface Vault: Receiver, Provider
?
On contract upgrade limitations, we have :
Removing an interface conformance of a struct/resource is not valid [0]
- What will happen when
Provider
in the example adds some incompatible change ? ( I know this is also currently a problem, but we will run into this more with this new proposal )
One solution comes to mind is to allow partial interface implementations, but it just came into my mind, I didnât think it over much. But main entry point is, interfaces are actually pre/post conditions ( from security pov, even though most contracts are not designed that in mind ) I get guarantee that, when something implements that interface, that methods will do what they say.
Considering something like (bear with me) :
pub resource interface SomeInterface: A, B{
}
pub resource interface A {
pub fun getSomeValue(): UFix64 {
pre/post here
}
}
pub resource interface B {
pub fun getName(): String {
pre/post here
}
}
if B adds pub fun getSomeValue(): String
we have a problem of conflict. Actually I would say pub fun getSomeValue(): UFix64
is even problematic, cause it can have totally different meaning. ( but it is deeper subject )
So maybe in that case, we can allow partial implementation, if I use variable as â@AnyResource{A}â I am bound to A interfaceâs pre/post conditions only. if I use as â@AnyResource{SomeInterface}â both.
If there is conflict, I get static or runtime error, when trying to use with SomeInterface
, but I still can use Bâs getName even it added pub fun getSomeValue(): String
my contract does not implements. ( hence the partial implementation )
So basically I signal which function pre/post conditions to use, when I implement a function.
pub resource Vault: SomeInterface { // Vault: A, B
//this comes from interface A
pub fun getSomeValue(): UFix64 {
pre/post here
}
//this comes from interface B
pub fun getName(): String {
pre/post here
}
//missing implemenatation from B: getSomeValue(): String
}
- Vault.getSomeValue() â runtime error conflict
- Vault.getName() â Bâs pre/post
- (Vault as @{B}).getName() â works, Bâs pre/post
- (Vault as @{A}).getSomeValue() â works, Aâs pre/post , returns UFix64
- (Vault as @{B}).getSomeValue() â runtime error, partial implementation error
This avoids common conflicts, if I store Vault
as a field typed @{A}
whatever B ( or SomeInterface ) adds doesnât break me, automatically.
But this still leaves the problem of default interface implementations. Leaving as a quick note here:
pub resource Vault: SomeInterface
Imagine A as Receiver, B as Metadata interface ( which is evil )
if Metadata interface adds a function withdraw
with default implementation to empty Vault. There is not much we can do. ( here is the problem of web of trust ) One solution can be to force SomeInterface to declare all methods from the interface B, and allow only those.
pub resource interface SomeInterface: A, B{
pub fun getSomeValue(): UFix64
pub fun getName(): String
}
here getSomeValue
and getName
will only move over. Even we can be more descriptive:
pub resource interface SomeInterface : partial A, partial B{
pub fun A.getSomeValue()
pub fun B.getName(): String
}
[0] Contract Updatability | Cadence