cadence icon indicating copy to clipboard operation
cadence copied to clipboard

Events emitted from interfaces

Open bluesign opened this issue 3 years ago • 10 comments

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

bluesign avatar Oct 14 '22 08:10 bluesign

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.

dsainati1 avatar Oct 14 '22 13:10 dsainati1

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.

bluesign avatar Oct 14 '22 15:10 bluesign

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

joshuahannan avatar Oct 14 '22 18:10 joshuahannan

@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)

turbolent avatar Oct 14 '22 21:10 turbolent

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 ?

bluesign avatar Oct 14 '22 21:10 bluesign

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.

turbolent avatar Oct 14 '22 22:10 turbolent

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} ?

bluesign avatar Oct 14 '22 22:10 bluesign

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

bluesign avatar Oct 14 '22 22:10 bluesign

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.

turbolent avatar Oct 14 '22 22:10 turbolent

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.

bluesign avatar Oct 14 '22 22:10 bluesign

Big +1 from me on this

bjartek avatar Oct 17 '22 07:10 bjartek

Would allow us to completely remove type requirements, related to: https://github.com/onflow/cadence/issues/1283

j1010001 avatar Oct 19 '22 18:10 j1010001

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

joshuahannan avatar Dec 15 '22 16:12 joshuahannan

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 )

bluesign avatar Dec 15 '22 17:12 bluesign

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

joshuahannan avatar Dec 15 '22 20:12 joshuahannan

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 )

bluesign avatar Dec 15 '22 20:12 bluesign

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.

bjartek avatar Dec 19 '22 09:12 bjartek

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

joshuahannan avatar Dec 19 '22 15:12 joshuahannan

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.

dsainati1 avatar Dec 20 '22 17:12 dsainati1

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

Exactly, otherwise it would be little strange for ftswitchboard etc for example. I think events when closer to storage mutation in general good idea

bluesign avatar Dec 21 '22 15:12 bluesign

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?

bluesign avatar Dec 21 '22 15:12 bluesign

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.

dsainati1 avatar Dec 21 '22 15:12 dsainati1

@bluesign have you gotten the chance to start on this FLIP yet? Do you need any help?

joshuahannan avatar Jan 26 '23 15:01 joshuahannan

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

cybercent avatar Feb 20 '23 15:02 cybercent

Sorry @joshuahannan I missed your message will start a FLIP asap

bluesign avatar Feb 20 '23 16:02 bluesign

@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.

bluesign avatar Feb 20 '23 16:02 bluesign

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. 👍

turbolent avatar Mar 23 '23 17:03 turbolent

Thanks @turbolent, I was gonna mae a flip then some work came I forgot , I will try to write this week

bluesign avatar Mar 23 '23 17:03 bluesign

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)

turbolent avatar Mar 23 '23 18:03 turbolent

FLIP: https://github.com/onflow/flips/pull/111

j1010001 avatar Jun 12 '23 21:06 j1010001