cadence
cadence copied to clipboard
Events emitted from interfaces
Issue To Be Solved
As we discussed in the language design meeting, I made a gist of what is on my mind.
https://gist.github.com/bluesign/2a654b7ce0cb889ff65447567e0ce069
Here the system works ( as of today ) with the trampoline function to emit the event. A language change allowing the event to be emitted directly from the interface can make it more straightforward.
I think with the addition of interface default implementations and interfaces conforming to other interfaces, we may end up with a really nice and less bloated contract code for FTs and NFTs.
@bjartek @turbolent @dsainati1 @joshuahannan @dete @janezpodhostnik
Just want to note that this would require a change to the view function feature: currently emitting an event is considered a non-view operation. This wouldn't block this feature, but we would need to change emitting an event to be allowed in view contexts in order to proceed here.
Just want to note that this would require a change to the view function feature: currently emitting an event is considered a non-view operation. This wouldn't block this feature, but we would need to change emitting an event to be allowed in view contexts in order to proceed here.
I think here (on interface decleration) allowing emit only, but still keeping function calls that are emitting events as mutating is also an option.
This seems like a fairly clean way to do it that I would support assuming the Cadence team doesn't have any issues with it
@bluesign Thank you for the example!
Allowing conditions to emit events is probably safe, as we had already discussed this previously in the view-functions FLIP that emitting events only has no visible side-effects on-chain in the program.
Are we OK though with how this feature (emitting events from conditions) would be used? In the example, the event emission function (emitTokenWithdrawal) can be called by any code, and with any type – the caller would not have to be a fungible token and could pass non-fungible token types. With the current approach we guarantee both (events are always FT events, are separate events)
Are we OK though with how this feature (emitting events from conditions) would be used? In the example, the event emission function (emitTokenWithdrawal) can be called by any code, and with any type – the caller would not have to be a fungible token and could pass non-fungible token type
Sorry, I don't get this part, can you explain a bit more ? I mean if someone implements FungibleToken Provider, and returns a FungibleToken vault, what else we can guarantee ?
Currently, contract interfaces can require concrete types to provide concrete types through the nested type requirements feature. For example, in the FT standard (pseudo-code):
contract interface FT {
event Deposit(amount: UFix64, ...)
}
contract ExampleFT: FT {
event Deposit(amount: UFix64, ...)
fun deposit(...) {
emit Deposit(...)
}
}
Here, ExampleFT.Deposit is a FT.Deposit event, and only code in ExampleFT can emit the event. If some code declares an event Deposit, it has no relation to FT.Deposit. So, non-fungible token code can not emit fungible token events.
In the gist, that is not guaranteed: Any code can call FungibleToken.emitTokenWithdrawal, even non-fungible token code. Also, a call with any run-time type is possible, e.g. FungibleToken.emitTokenWithdrawal(type: Type<Int>(), ...) is valid – but Int is not an FT event type.
How can it be called with Type<Int>()?
access(contract) fun emitTokenWithdrawal(type: Type, amount: UFix64, from: Address?): Bool{
emit TokenWithdrawal(type:type, amount:amount, from:from)
return true
}
This is only called from :
pub resource interface Provider{
pub fun withdraw(amount: UFix64): @{FungibleToken.Vault} {
post {
result.balance == amount:
"Withdrawal amount must be the same as the balance of the withdrawn Vault"
//TODO: this can be converted something allows emit directly
// emit TokenWithdrawal(type:result.getType(), amount:result.balance, from:self.owner?.address)
FungibleToken.emitTokenWithdrawal(type:result.getType(), amount: result.balance, from: self.owner?.address)
}
}
}
How will Int implement @{FungibleToken.Vault} ?
Here, ExampleFT.Deposit is a FT.Deposit event, and only code in ExampleFT can emit the event. If some code declares an event Deposit, it has no relation to FT.Deposit. So, non-fungible token code can not emit fungible token events.
Isn't this also an advantage? Currently if you put your NFT into some generic Collection, there is no guarantee that there will be any Deposit/Withdrawal events for NFT
Also I made a suggestion of adding a Source field to event [0] , though I am not so sure on that.
[0] https://gist.github.com/bluesign/2a654b7ce0cb889ff65447567e0ce069?permalink_comment_id=4335528#gistcomment-4335528
Isn't this also an advantage? Currently if you put your NFT into some generic Collection, there is no guarantee that there will be any Deposit/Withdrawal events for NFT
Interesting, I didn't think of that. Then again the collection itself should probably emit a separate event.
Not arguing either way, was just raising the question to better understand if we want to continue to keep those constraints, or if we are OK with loosening them and trade some safety for some flexibility.
Yeah it is a valid point, but safety part I didn’t get; eventually if you implement some interface, this interface enforcing an event. Shouldn’t it be on interface type? Eventually they are firing the event.
Big +1 from me on this
Would allow us to completely remove type requirements, related to: https://github.com/onflow/cadence/issues/1283
Based on the poll here, it looks like there is a decent amount more support for this change than for event inheritance. So we should probably move forward with a FLIP for this feature? Is anyone willing to help write up the FLIP for it? I can update the token standards proposal to include this feature also once we decide how we want it to work
I can write up FLIP if necessary. To be clear, events will be emitted in the interface instead of implementer right? Post condition will call event emitter. (Event emitting will be non mutating etc )
I just thought of something. We've thought about including type information and important metadata views in the standard events. If we have the interfaces emit the events, will that information be available to the events? I don't see how the event could get any information besides the id/amount/receipient/owner
Shouldn't interface inherit metadataviewresolver (or till inheritance deployed implement) ?
It shouldn't be a problem for rich events ( @bjartek may know more on this )
the interface should know about inputs/outputs and on NFT since they are NFT resources they should implement the views resovlers so you can call them to get more data out of it.
oh, I was assuming that we'd put the events in the Receiver and Provider interfaces, but it sounds like you're saying they should be in the Vault and Collection interfaces instead so they have access to those methods. I guess that makes sense since those are where the events are usually emitted from now anyway
Is this proposing that interfaces would inherit events from each other, but composites wouldn't inherit from interfaces? That seems inconsistent; the behavior for the two should be the same.
oh, I was assuming that we'd put the events in the
ReceiverandProviderinterfaces, but it sounds like you're saying they should be in theVaultandCollectioninterfaces instead so they have access to those methods. I guess that makes sense since those are where the events are usually emitted from now anyway
Exactly, otherwise it would be little strange for ftswitchboard etc for example. I think events when closer to storage mutation in general good idea
In my example there is no inheritance. Each interface is responsible with firing its own events. Can you expand a bit what do you mean?
I was referring to @joshuahannan's question above; if the events are defined in Receiver and Provider but emitted from Vault and Collection, how would these subtype interfaces access the supertypes' events. But it sounds like that isn't the proposal in the first place, so maybe a moot question.
@bluesign have you gotten the chance to start on this FLIP yet? Do you need any help?
Currently if you put your NFT into some generic Collection, there is no guarantee that there will be any Deposit/Withdrawal events for NFT.
Would this FLIP solve the issue @bluesign mentioned above?
This can be problematic because certain occurrences might be mistakenly labeled as Mint or Burn events.
Example using Flovatar: https://flowscan.org/transaction/e67ee54cea9f74dff6816a1eebcd894a1a2dba78dd2f06a190159508aed6a299
Sorry @joshuahannan I missed your message will start a FLIP asap
@cybercent yeah this will handle situations like this. Basically events will not change, just they will not be fired from Flovatar but will be fired from interface.
I think this way we can add Burn event on destroy for example or Mint event on create ( init ) in the standard. It is not so easy to trace who owns the NFT if you put it into another resource. ( or contract ) because then it is going out of the standard somehow.
I just had another look at this proposal, and it makes a lot more sense to me know (I think I misunderstood it at first).
@bluesign re: https://github.com/onflow/cadence/issues/2069#issuecomment-1279526714 thanks for pointing out that the helper function is private, I missed that 👍
From what I can see and understand, this pattern, as demonstrated in the gist, could be adopted by Cadence programs today, and there are not language changes necessary. As Deniz pointed out, we could make this even easier, by changing Cadence and allowing emit statements in post conditions, correct?
This seems like low hanging fruit, and I'd personally be happy to see a FLIP for this. 👍
Thanks @turbolent, I was gonna mae a flip then some work came I forgot , I will try to write this week
We can maybe split up what's proposed in the issue in separate proposals:
- Allow emit statements in post conditions (just convenience, can already be done today with a helper contract and contract function returning true)
- Allow declaration of events in contract interfaces (also not strictly necessary, but "convenient", because that avoids the need to have to declare a contract in addition to the contract interface)
FLIP: https://github.com/onflow/flips/pull/111