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

Allow Solid state transfers #2013

Closed
wants to merge 6 commits into from
Closed

Conversation

shargon
Copy link
Member

@shargon shargon commented Oct 22, 2020

Close neo-project/proposals#97
Original Idea from: @igormcoelho

TODO: test

@shargon shargon marked this pull request as ready for review October 22, 2020 17:18
if (BitConverter.ToUInt32(script, index) != ApplicationEngine.System_Contract_Call)
return false;
index += 4;
if (script[index++] != (byte)OpCode.EQUAL) return false;
Copy link
Contributor

@igormcoelho igormcoelho Oct 22, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can let both "==" OR ">=", as in comment neo-project/proposals#97 (comment) , OpCode.EQUAL or OpCode.GEQ (this allows massive parallel executions for exchanges).

}
else
{
// Other pushes
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can try to simplify here, by restricting to some general PushInt operation... do we have that?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is that script builder generate the most optimized one

byte[] solidTransferScript;
using (ScriptBuilder sbSolid = new ScriptBuilder())
{
sbSolid.EmitPush(value);
Copy link
Contributor

@igormcoelho igormcoelho Oct 22, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have some "default" guarantee for EmitPush value? (just accepting large values opcode is enough) If we can make a standard here, then it simplifies above multiple "numeric matches". Maybe just adopt some large push alternative, which is enough for most people. Witnesses can be pruned on Neo3, so this won't cause big damage.

{
sbSolid.EmitPush(value);
sbSolid.EmitAppCall(output.AssetId, "balanceOf", account);
sbSolid.Emit(OpCode.EQUAL);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we default this to .EQUAL or to .GEQ?
Maybe, .GEQ is better, because parallel transfers would happen on Neo Wallet "by default". I think that, other user wallets (maybe Neon, etc), could focus on .EQUAL standard here, because use case is very targetted for non-automatic transfers (which is not true for Neo, as the "core Neo library").

{
sb.EmitPush(12);
sb.EmitAppCall(UInt160.Zero, "balanceOf", UInt160.Parse("01ff00ff00ff00ff00ff00ff00ff00ff00ff00a4"));
sb.Emit(OpCode.EQUAL);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we try some .EQUAL and some .GEQ? It's good to have both tested.

Copy link
Contributor

@igormcoelho igormcoelho left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amazing and quick progress @shargon, congratulations!
The general comments are towards two things:

  • We should adopt .GEQ by default (instead of .EQUAL), because this allows parallel transactions (balance "at least larger than" X)
  • Is it possible to make a numeric standard using larger numbers during EmitPush, to prevent that many "small cases"? This may be slightly worse from space efficiency perspective, but so much better from code maintainance perspective. I favor code maintainance in this case (so small difference in byte push is not worth it)
    Congratulations again!

@@ -298,6 +301,32 @@ public Transaction MakeTransaction(TransferOutput[] outputs, UInt160 from = null
}
sb.EmitAppCall(output.AssetId, "transfer", account, output.ScriptHash, value);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that "value" has two meanings here:

  • The "value" being transferred, which is different from balance (good for OpCode.GEQ operations)
  • The actual balance from the account, that must be previously taken from the wallet system (good for OpCode.EQUAL operations).

Maybe it's nice to proceed from this point with two values: "transferValue" and "balanceValue".

using (ScriptBuilder sb = new ScriptBuilder())
{
sb.EmitPush(-1);
sb.EmitAppCall(UInt160.Zero, "balanceOf", UInt160.Parse("01ff00ff00ff00ff00ff00ff00ff00ff00ff00a4"));
Copy link
Contributor

@igormcoelho igormcoelho Oct 22, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have some test where Balance was not Zero before? I mean, guy has 10 tokens, is sending 6... thus, balance should be 4 (in case of OpCode.EQUAL); and assertion should be ">=6", if using OpCode.GEQ.
Let's call these two strategies "strict" (for OpCode.EQUAL) and "flexible" (for OpCode.GEQ), just to clarify between them.

using (ScriptBuilder sbSolid = new ScriptBuilder())
{
sbSolid.EmitPush(value);
sbSolid.EmitAppCall(output.AssetId, "balanceOf", account);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If so, we also need to update here, CallFlags.AllowStates -> CallFlags.ReadOnly.

using (ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, verifiable, snapshot?.Clone(), gas))
{
CallFlags callFlags = verifiable.Witnesses[i].StateDependent ? CallFlags.AllowStates : CallFlags.None;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO it's much easier to unconditionally do callFlags = CallFlags.ReadOnly and allow any verification scripts to access storage. This would then allow this feature to be a wallet feature not requiring anything special support from the ledger and allowing to remove IsSolidTransfer helper completely (except fee calculation?). It would also of course allow to have other interesting state-dependent verification scripts.

Witness StateDependent flag could then be set based on IsStandardContract(), keeping this optimization in place for the most common case.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Completely agree

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also agree... from the perspective on Neo, there are so many applications that require reading from storage, and it's not good to make exceptions to each of them. Best approach is to allow storage reads, and deal with the fact that transaction ordering will affect future verifications.

@erikzhang
Copy link
Member

What problem does this mechanism solve?

@igormcoelho
Copy link
Contributor

What problem does this mechanism solve?

This mechanism ensures that nodes in wrong versions or with poor implementations of Neo protocol, will fail to verify blocks on network, and will be interrupted (since verifications cannot fail). This is complementary to MPT technology, but much easier to track on wallets/explorers, since it doesnt depend on storage implementation prefixes, etc.

@igormcoelho
Copy link
Contributor

igormcoelho commented Oct 24, 2020

@shargon I've been thinking, if we want stateless verifications (I need to check code again to refresh my mind on currently adopted state limitations), then we should just drop this PR and NEP, at least for Neo3. So maybe move for master 2, and leave just MPT to do the job on Neo3, do you agree @shargon ?

@shargon
Copy link
Member Author

shargon commented Oct 26, 2020

@igormcoelho If we have stateless verification, how can you change the owner of a regular smart contract?

@shargon shargon closed this Feb 8, 2022
@shargon shargon deleted the solid-transfer branch February 8, 2022 10:04
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

Successfully merging this pull request may close these issues.

Solid State Transfers
5 participants