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

Add locking functionality to Noto #455

Open
awrichar opened this issue Nov 27, 2024 · 8 comments · May be fixed by #483
Open

Add locking functionality to Noto #455

awrichar opened this issue Nov 27, 2024 · 8 comments · May be fixed by #483
Labels
enhancement New feature or request stale

Comments

@awrichar
Copy link
Contributor

What would you like to be added?

Add functionality to Noto for locking a particular amount of value, such that a transaction can be built and guaranteed to be successful when certain conditions are met.

Why is this needed?

Currently Noto supports a pre-approval flow (where an owning party can approve some of their tokens to be spent by another), but it provides no way to guarantee that the flow will be possible. The input states are still available after a particular transfer has been approved - so they may end up getting consumed by another transaction before the approved transfer is able to execute. This lack of an execution guarantee prevents many types of trades and cross-chain flows from being implemented with Noto.

@awrichar awrichar added the enhancement New feature or request label Nov 27, 2024
@awrichar
Copy link
Contributor Author

Below is a potential implementation of this feature. It requires adding a new "locked" attribute to Noto states, and defining a way to lock and unlock them, ensuring that the following conditions are met:

  • anytime value changes owners, the notary must be involved
  • the logic around locks must be programmable, so it must be possible to implement in a shared ledger contract without involving the notary directly in the logic
Paladin_-_Miro

@awrichar
Copy link
Contributor Author

awrichar commented Dec 14, 2024

Some additional points after further thought on this design:

  • Either the existing Noto coin schema must be altered to include a "locked" boolean in its data, or a new state schema must be introduced (such that B is not a regular coin, but is a new "lock" or "locked coin"). I am leaning toward the latter.
  • There does not appear to be any need to make Paladin core aware of how locking works - it seems like it can be knowledge held by the domains only, and is just a special state or state attribute.
  • It should not be required to specify D ahead of time. Only A, B, C must be specified up-front - D may be designated later. In fact, it should be possible to designate any number of possible "success" outcomes, as long as they are all validated by the notary. It should also be possible to remove an outcome. Adding/removing outcomes to the lock must always result in a base ledger transaction.
  • The base ledger Noto contract will need knowledge of current locked states and all of their possible outcomes.
  • In the special case where a Pente private contract is used for notary hooks, it will need new hooks for each Noto operation discussed here (create lock, add lock outcome, remove lock outcome, unlock). The private contract must track the state of all open locks and their possible outcomes - and must ensure that all possible outcomes remain valid. If another operation on the private contract causes a previously-specified outcome to become invalid, the private contract may trigger the Noto base ledger contract to remove that outcome possibility.

The last point above is particularly important in the case that Pente hooks are being used to enforce policies. If I lock a $100 state, and then add a possible outcome that I would like to transfer it to Bob, but then Bob's account is later frozen before I resolve the lock, the hooks contract must have a way to remove the "transfer to Bob" as a valid outcome for the lock at the moment he is frozen. The hook contract would need to maintain a map of all locked/potential transfers by recipient address, and purge the relevant entries if an account is frozen. Additional logic could be added to allow the lock to become valid again if that account is unfrozen.

In the case of policies involving balances, the hook contract would need to track min/max values for all confirmed balances along with any possible outstanding debits and credits from in-flight locks, and ensure that all possible outcomes would remain in the allowed range.

@awrichar
Copy link
Contributor Author

awrichar commented Dec 14, 2024

There's still a bit of an odd window between these two steps:

  1. the point where the lock delegate selects which outcome has become "true"
  2. the point where someone instructs the notary to "unlock" and therefore move all the private/public states into the correct shape

Perhaps (1) needs to emit an event, and the notary should have logic to listen for that event and then trigger (2).

@peterbroadhurst
Copy link
Contributor

peterbroadhurst commented Dec 15, 2024

This definitely feels in the right direction to me.

I just couldn't convince myself we've covered all the angles yet.

I'm really focussed on the Pente-backed-notary case, because it's the most complex, but also because I think it's the most pure and attractive way to implement a notary pattern.

I can see that using a Noto+Pente transaction to enter the locked state works. However, I can't yet see how the completion transaction can be atomic.

Struggling to put it into words, but my assumption of how it would work is as follows:

  1. OrgA, owner of CoinA creates a Pente privacy group with Smart Contract address 0xAAA
  2. OrgA performs a Noto(+Pente) transaction to swap CoinA for CoinA1
    • Notary privacy group marks some value "locked by 0xAAA" in the Notary-only privacy group
    • Two pre-signed outcome transactions are prepared that can execute without involvement of the notary contract
      1. CoinA1 -> CoinA2 - owned by OrgB
      2. CoinA1 -> CoinA3 - owned by OrgA, but no longer locked
  3. OrgA executes the path via a transition in 0xAAA - let's say CoinA2 path (success)
    • At this point the Noto smart contract is out of sync with the Notary smart contract on-chain - Noto is ahead
    • OrgB will not be able to spend the coin they now own, until the Notary does a further blockchain txn
    • Event is emitted by Noto in this case for immediate action by the Notary
  4. The Notary submits a new Pente-only smart contract call
    • This removes the lock state in the Notary contract, thus allowing the spending to happen

How did I do?

The fact this this flow does not allow a coin to be immediately spendable by OrgB is awkward, but possibly unavoidable.

There is a lot of detail to work out on which Paladin TransactionIDs get confirmed at which points, and how the steps are orchestrated. But this does seem like a plausible way forwards.

@awrichar
Copy link
Contributor Author

@peterbroadhurst That's mostly what I've proposed, except that I'm proposing state CoinA2 doesn't get confirmed until step (4). In step (3), we settle that the only possible resolution of the lock will be CoinA2, and emits an event indicating so, and the notary must respond to that event by executing (4) which is a Noto+Pente call that confirms CoinA2 and purges other possible outcomes, and keeps Noto in sync with the notary's private contract logic. I believe this can be enforced on the base ledger Noto contract so that once (3) has executed, no other transition of CoinA1 will be possible except to spend it and confirm CoinA2.

The slight alternative you've outlined is that CoinA2 is confirmed earlier, as part of (3). This has some attractive qualities in that it feels a little bit more atomic (as the Noto transfer itself can be part of another base ledger transaction that doesn't involve the notary). However, the resulting coin is still not really spendable unless (4) happens next, so I don't think the ownership guarantees and atomicity are fundamentally different than the way I've outlined it (unless the notary misbehaves, in which case nothing matters anyway). And the limbo here with Noto doing a state confirm out of sync with the Pente notary logic feels slightly problematic to me.

@awrichar
Copy link
Contributor Author

awrichar commented Dec 16, 2024

After thinking some more, I can see an argument for making the Noto state confirmation happen in (3), particularly for the purposes of ownership tracking. If it's important to know the exact moment that Org B took ownership of the value (and it certainly is), then it's useful to reflect the Noto state change in (3). In DvP or PvP scenarios, this would ensure that both legs of the transaction are recorded together.

There are still some difficulties with this approach, and it means accepting that the private notary contract may get temporarily out of sync with Noto. But it also reinforces that Noto is the true ownership record.

@awrichar awrichar linked a pull request Dec 17, 2024 that will close this issue
@awrichar
Copy link
Contributor Author

As I've started working through this and related ideas on #483 and #485, I believe the API can be generalized as follows.

Private ABI of Noto:

  • lock(bytes32 id, uint256 amount, bytes data) - Create a new lock using the specified amount of the owner's tokens.
  • unlock(bytes32 id, string[] to, uint256[] amounts, bytes data) - Unlock the specified amount of value from a lock, sending it to the recipients. The total value unlocked must be less than or equal to the total locked amount. If less, the leftover value remains locked. Can only be called by the lock creator, and only if the lock has not been delegated.
  • approveUnlock(bytes32 id, string to[], uint256 amounts[], bytes data) - Approve an unlock operation that can be executed by the lock delegate. Can only be called by the lock creator, and only if the lock has not been delegated.
  • delegateLock(bytes32 id, address delegate) - Delegate unlocking to another address. Once delegation is exercised, control of any remaining locked value is returned to the lock creator.

I believe the "lock outcomes" outlined previously are implementable as "approveUnlock" transactions. Additional thoughts on approvals:

  • We may want a batch version of "approveUnlock", to register multiple approvals for different unlock outcomes all at once (only one of which will be able to be chosen).
  • There are use cases for combining some calls - such as "lock and approve" or "approve and delegate". For now I've only proposed them as discrete operations, which is most similar to existing token standards.
  • "approveUnlock" must retain the characteristic that the "unlock" can be exercised on the base ledger, without the need to go back through the notary.
  • We may need a spelling for removing an approval. Many standards spell this by reusing the "approve" call with a boolean parameter or a 0 amount, rather than having a separate method for it.

When combined with Pente hooks, this set of operations should also allow implementing custom patterns similar to the one proposed on #485. More on hooks:

  • We need to ensure that the data parameter is passed in to hooks. This can be used for adding custom logic to hooks.
  • The default checks on lock creator should be disabled when hooks are used. This can allow parties other than the lock creator to unlock portions of value if they meet specific (custom) criteria.

Copy link

This issue is stale because it has been open 30 days with no activity.

@github-actions github-actions bot added the stale label Dec 28, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request stale
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants