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

Unable to exit Token Type atomically when all tokens are redeemed #196

Open
amoothart opened this issue Apr 8, 2020 · 8 comments
Open

Comments

@amoothart
Copy link

Both Fungible Tokens and their corresponding Token Type cannot be redeemed in the same Corda transaction. Trying to do so results in an error where a state cannot be both a references and an input: https://github.com/corda/corda/blob/release/os/4.4/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt#L644

However this seems like a valid use case when a non-fungible token (eg. a house) is redeemed and the token type has no remaining functional value because there are no tokens remaining. The token type should be closed because the asset would have to be re-issued onto ledger to gain functional value again.

Preferably the token type and the underlying tokens are exited simultaneously but currently the token type has to be done in a second transaction in a non-atomic fashion. The same problem occurs for redeeming fungible tokens which represent fractional ownership of a non-fungible asset.

What is the intent for redeeming token types when all underlying tokens have been redeemed?

Is it safe to "evenutally" exit all token types once their tokens have been redeemed?

@gendal
Copy link

gendal commented Apr 9, 2020

Hey Austin - I'll let the tokens team (@IvanR3 ?) comment on the specifics. But to the general point, this would seem to be the right design at the platform level I think... I mean: including a state as an input means "if this tx is valid and the input is currently unspent then please allow this tx to be confirmed and mark this state as spent". Including it as a reference means "if this input is currently unspent then please allow this tx to be confirmed but do NOT mark it as spent". That's logically inconsistent, right?

So it feels like this is a Tokens SDK question rather than a core platform question related to how ref states work? eg if you DO need the ability some times to exit something that functions as a ref state maybe the solution is to allow it to be included as a regular state - ie it's a question of how certain types of txs are constructed by the SDK? (cc @roger3cev for info)

@roger-that-dev
Copy link

@amoothart EvolvableTokenTypes were created as irredeemable as the issuer of them doesn't necessarily always know who's using them. They also don't take up that much space so I didn't think it matters so much if they hang around on the ledger. In the event that all tokens -- from all issuers -- have been redeemed then it's safe to redeem the EvolvableTokenType state. However, let's say there is a "10 Year T-Bond" EvolvableTokenType. It could be used by any issuer and indeed, it's good if it is because that way you can select all 10 Year T-Bond fungible tokens across a range of issuers. The trade-off is that the issuer of the EvolvableTokenType won't necessarily know when it is safe to remove it from the ledger. So... it's best to just make it irredeemable. Cheers

@amoothart
Copy link
Author

Let's take a concrete example where redeeming EvolvableTokenType could be helpful. For a housing Cordapp a BNO may create thousands of EvolvableTokenTypes to describe each individual property. In the event that someone buys all tokens for a property and redeems them there is no point for that house's EvolvableTokenType to exist anymore. And unlike the 10 Year T-Bond example each house is different so the only case in which tokens will exist again is if the same house is re-issued back to the ledger.

Are you suggesting that the Token SDK or the cordapp should make EvolvableTokenType irredeemable? In testing I was able to exit the token type and I believe this is valuable because there won't be EvolvableTokenType visible which aren't truly accessible.

Thanks for the guidance!

@adelrustum
Copy link

I was receiving emails about this discussion (since I'm subscribed to this repo), and it only hit me today during my afternoon nap lol
Austin, I think the solution to your scenario is the following:

  1. Create a SchedulableState (call it HouseTokenTypeRedeemer).
  2. Inside your flow that redeems your house token, instead of using the ready flows (i.e. RedeemNonFungibleTokensFlow), use the utility functions (i.e. addRedeemTokens()); this way you can craft your transaction manually and include in it the following inputs:
    • The house NonFungibleToken
    • The HouseTokenTypeRedeemer state
  3. Using the above approach will make consuming both states an atomic event (either both get consumed or none).
  4. Now back to your HouseTokenTypeRedeemer schedulable state, you can schedule that state to kick off an activity after some period of time (let's say 5 minutes), and inside its nextScheduledActivity() method, you can have it start a flow that consumes the HouseTokenType.

So in summary, you'll have an atomic transaction that consumes your house NonFungibleToken and HouseTokenTypeRedeemer states, and a flow that starts after 5 minutes to consume the no longer needed HouseTokenType.

You can find an example on scheduleable states here.

@adelrustum
Copy link

Austin, I'm not sure how you were able to exit your EvolvableTokenType; because it only has Create and Update commands; see here.
My only assumption is that when you extended EvolvableTokenContract, you introduced an Exit command and you also overridden the verify() method (which we can see here only expects a Create or an Update command).

Btw, I do agree with you that it should have an Exit command, because following this definition of NonFungibleToken from the Tokens SDK design document:

There is no Amount property in this class, as the assumption is there is only ever ONE of the IssuedTokenType provided. It is up to issuers to ensure that only ONE of a non-fungible token ever issued

I believe that adding an Exit command, makes the above requirement easier to implement.

@amoothart
Copy link
Author

amoothart commented Apr 14, 2020

Great proposal @adelrustum! I will give this a try and let you know how it goes. Having to maintain a secondary HouseTokenTypeRedeemer state throughout the lifecycle of the House token seems heavyweight since it's only value is to initiate the TokenType redeem flow.

Rather than exiting a HouseTokenTypeRedeemer state on token exit this should be when we create this redeemer. So in your step 2 this would be an output state rather than an input. Then inside the HouseTokenType redeem flow the redeemer state would be exited. All this has to be verified but I'll give it a try.

And yes, I added an Exit command to the TokenType and may end up subclassing EvolvableTokenContract to add an additionalExitChecks method.

@adelrustum
Copy link

Austin, I'm a bit confused about your approach (I think there a couple of typos):

  • when we create this redeemer -> when we create this token
  • then inside the HouseTokenType redeem -> then inside the HouseToken redeem

What I meant in my initial answer is the following:

  1. When you issue the NonFungibleToken, you also create a related HouseTokenTypeRedeemer.
  2. When you redeem the NonFungibleToken, you exit (consume) the HouseTokenTypeRedeemer which will trigger the flow that will exit your HouseEvolvableTokenType.

@roger-that-dev
Copy link

roger-that-dev commented Apr 14, 2020

As mentioned above, you can't generally have an exit command because you don't know who else is using the token type. Imagine your business depended on - to some degree - my token definition which I then redeem. You'd be a bit screwed then. Considering token definitions don't take up much space etc. you may as well have them on the ledger. I do not recommend redeeming them. Of course, it's up to you but please don't then come back here saying you can't move tokens because the token definition doesn't exist anymore and therefore your app is broken. Also, the whole point of reference data is that it hangs around forever. It's not ephemeral. Cheers

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

No branches or pull requests

4 participants