Skip to content

Implement NEP-7#172

Merged
erikzhang merged 15 commits intoneo-project:masterfrom
ConjurTech:nep-7
May 9, 2018
Merged

Implement NEP-7#172
erikzhang merged 15 commits intoneo-project:masterfrom
ConjurTech:nep-7

Conversation

@RavenXce
Copy link
Copy Markdown
Contributor

I've done an implementation of NEP-7 since it seems to be stuck for awhile:
https://github.com/neo-project/proposals/pull/15/files

Think this should be done earlier rather than later so later contracts don't break. Right now most contracts are just ICO contracts so breakage is minimal.

I just did this in the most straightforward way possible, please advice if there is a better way.

@erikzhang erikzhang self-requested a review February 13, 2018 09:03
erikzhang
erikzhang previously approved these changes Feb 26, 2018
@dicarlo2
Copy link
Copy Markdown

NEP-7 mentions backwards compatibility in the sense that if a contract does not respond correctly to the VerificationR or ApplicationR triggers then it will just fail and be a no-op. This definitely makes sense for the ApplicationR trigger, but the VerificationR trigger will cause consensus to not include the transaction at all in the block (in fact, well-behaved nodes will not relay the transaction across the network). Seems like this may be problematic for ongoing ICOs because presumably the contract will always return false if they do not recognize the 'receiving' method, so mintTokens will fail. Is my understanding correct?

@localhuman
Copy link
Copy Markdown
Contributor

I agree with @dicarlo2.

Also, this is a very large change. A change such as this should have tests. Tests to show what happens with old contracts. Tests with new contracts that respond to R. When you say 'breakage is minimal' we need to know exactly what that means.

@RavenXce
Copy link
Copy Markdown
Contributor Author

RavenXce commented Feb 26, 2018

@dicarlo2 Yes, since most people return false when not matching any TriggerType, it should fail. ICOs that are running when this version is about to be deployed should cater to it? The change would require a slow rollout, so that everyone is aware. But doing it earlier is better than later, when there may be many contracts that will be receiving constantly, rather than just during a short period of time.

@erikzhang The current implementation now invokes a set of R triggers for each TransactionOutput to a single contract, rather than just one VerificationR and InvocationR per contract per txn. The NEP is not precise on what is the expected result.

I don't really see the upside of triggering multiple times, and the downside is that it is harder to count the total output as there is no assurance of a single trigger per transaction. Could you explain the use case for that?

@RavenXce
Copy link
Copy Markdown
Contributor Author

RavenXce commented Feb 26, 2018

@erikzhang A possible modification if we invoke once per TransactionOutput would be to update the spec such that the txn output (or index) is passed in as a parameter to the invocation rather than just leaving it empty. This would make counting the received assets very efficient within the SC itself.

Edit: Sorry, this doesn't really make sense for VerificationR either actually - typically only the total is useful for verification.

@localhuman localhuman self-requested a review February 26, 2018 13:49
@RavenXce
Copy link
Copy Markdown
Contributor Author

RavenXce commented Feb 26, 2018

@localhuman the change is not meant to be backward compatible with old contracts, as mentioned in the NEP. I'm just saying we should push forward with this, if this is ever going to be done, such that there is less incompatible contracts out there.

@localhuman
Copy link
Copy Markdown
Contributor

Are you saying that if we rebuild the chain with these changes, we will get different results than what is currently there?

If that is the case, I think its foolish. But either way there should be tests to show how and where things will break.

@localhuman localhuman requested a review from AshRolls February 26, 2018 13:53
@@ -3,6 +3,8 @@
public enum TriggerType : byte
{
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Would it be better for this to be an enum style bit mask?

That way if an existing contract checks for Verification, even if the contract is VerificationR it will match.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hmm, since Verification is typically just a CheckWitness of the contract owner, and this is a receive rather than a send, existing contracts will just fail too with false.

@RavenXce
Copy link
Copy Markdown
Contributor Author

RavenXce commented Feb 26, 2018

@localhuman no, we will need a way to prevent that from happening. I don't see a clean way of doing it though, besides activating by blockheight - any suggestions?

Edit: by the way, I didn't see any test harness for running contracts either - where do you suggest doing it?

@RavenXce
Copy link
Copy Markdown
Contributor Author

Another way to completely preserve backward compatibility would be to add a new ContractPropertyState...

Copy link
Copy Markdown
Member

@AshRolls AshRolls left a comment

Choose a reason for hiding this comment

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

As per @localhuman comments this PR needs more thought before we can merge.

@canesin
Copy link
Copy Markdown
Contributor

canesin commented Feb 26, 2018

I think NEP-7 shouldn't exist at all. What is the case that this allow that cannot be done in current system in two transactions ? Breaking changes in every single contract should have a very strong motivation, this is not one.

@RavenXce
Copy link
Copy Markdown
Contributor Author

One example is a contract that does not allow any deposit, and so guards against any accidental sends to it.

Once again, by adding a contract property, we can prevent all old contracts from breaking.

So I think it is possible to move forward in a safe way.

Please take note that this nep has nothing to do with withdrawals from a contract. So two txns or not is not relevant here.

@canesin
Copy link
Copy Markdown
Contributor

canesin commented Feb 26, 2018

@RavenXce you can reject the transaction forcing verification to run today, Narrative ICO and Bridge ICO did that already and any project that requires the feature can do the same. You can do it in Switcheo platform. The only guard is for accidental sends away from any reasonable interface. No need for breaking changes.

Besides breaking changes, there is zero tests - both passing and expected failures.

@RavenXce
Copy link
Copy Markdown
Contributor Author

RavenXce commented Feb 26, 2018

@canesin that is indeed correct, assuming the client does the correct things.

We have already implemented that awhile back: https://github.com/ConjurTech/switcheo-token/blob/master/Switcheo_Token/Switcheo_Token.cs#L46

However, I feel that this is a useful change, and is akin to the payable modifier in Ethereum. I do not think that just because something can be done through clever workarounds means that the platform should not try to improve on it for SC developers. Developer UX matters too.

I feel this will be a massive improvement to the current two triggers, which is hard to get right due to the mixing of concerns. If you look at the code snippet I posted above, I'm sure you can see that it is extremely hard to reason about the different possible cases.

To reiterate, I've suggested a way to deviate from the original proposal such that the change is non-breaking. Regarding tests, I don't see any real tests for any case in any platform currently (unless I'm mistaken). If someone would give me some direction, I would be most glad to add them - my first thought when implementing this was actually to do so! Starting this endeavor would be good for the platform as a whole anyway.

Anyway, since this NEP was proposed by @erikzhang, I will leave it up to him to decide if he wishes to close it, and will wait for some consensus before proceeding with further changes.

@RavenXce
Copy link
Copy Markdown
Contributor Author

RavenXce commented Feb 26, 2018

I don't wish to be argumentative, but I would like to bring up another example, which I just remembered.

Here I have to guard against double counting of received assets, bloating up the code, and storing a value that is only used once:
https://github.com/ConjurTech/switcheo-token/blob/master/Switcheo_Token/Switcheo_Token.cs#L259
(In this example, I wish to allow multiple contributions within the ICO period. Let us assume that in a more complex contract, I also do not want to force this to be a TailCall, so checking of InvocationScript in Verification stage is not viable)

Overall, the whole counting of assets issue is a very easily missed attack vector, and there should probably be a proper, sane way to do it?

@erikzhang
Copy link
Copy Markdown
Member

@localhuman

Are you saying that if we rebuild the chain with these changes, we will get different results than what is currently there?
If that is the case, I think its foolish. But either way there should be tests to show how and where things will break.

The results will not be different. Because VerificationR will only run when the transaction is being relayed or during the consensus. And transactions that have been included in the block will not trigger a second consensus.

@erikzhang
Copy link
Copy Markdown
Member

@RavenXce

The current implementation now invokes a set of R triggers for each TransactionOutput to a single contract, rather than just one VerificationR and InvocationR per contract per txn. The NEP is not precise on what is the expected result.
I don't really see the upside of triggering multiple times, and the downside is that it is harder to count the total output as there is no assurance of a single trigger per transaction. Could you explain the use case for that?

You are right. One contract in each transaction should be called only once. But we must make sure that ApplicationR are invoked in a deterministic order.

I will do the changes.

@shargon
Copy link
Copy Markdown
Member

shargon commented May 16, 2018

And we need to test with old contracts

@erikzhang
Copy link
Copy Markdown
Member

Okey, let's temporarily disable this feature on the mainnet and deploy it on testnet first.

@canesin
Copy link
Copy Markdown
Contributor

canesin commented May 16, 2018

@erikzhang can't we treat this with more specificity ? For example: "It will be deployed in testnet on block X and if successful on mainnet on block Y." If you can't give block numbers, dates and times.

@jsolman
Copy link
Copy Markdown
Contributor

jsolman commented May 24, 2018

The results will not be different. Because VerificationR will only run when the transaction is being relayed or during the consensus.

Since this stops relaying transactions this needs to be gated by block height so as to not block currently running ICOs. If it’s not gated by block height in the code people will deploy at various different times and break investing in various ICOs that don’t implement VerificationR. I want to run with other improvements on mainnet, so I’ve just disabled VerificationR and ContractPayable checks while running with other improvements.

@localhuman
Copy link
Copy Markdown
Contributor

localhuman commented May 24, 2018

Agreed, this ( in conjunction with the Contract IsPayable ) should be versioned. Anyone who needs to rebuild the chain should be able to achieve historical results.

Doing a check by block height is not ideal, as this would not translate to private/test other networks. Having a version property for Contracts that defaults to 1 could be a solution, with any new contracts required to be 2 or greater.

@jsolman
Copy link
Copy Markdown
Contributor

jsolman commented May 24, 2018

I think @erikzhang already pointed out rebuilding the chain doesn’t have issues since the methods only run during consensus, not when resync of the chain.

I get your desire though to not stop existing contracts from working by versioning the contracts. Seems like a good idea to me and does require someone to take up the work for more changes to handle contract versioning, etc.
I’m not volunteering for this now, but would be good if we knew who was going to work on it.

@shargon
Copy link
Copy Markdown
Member

shargon commented May 25, 2018

I think @erikzhang already pointed out rebuilding the chain doesn’t have issues since the methods only run during consensus, not when resync of the chain.

All contracts are executed by all nodes when you get the blocks from the begining, so the result must be the same, otherwise if you start without chain, and get the blocks from others nodes, your storages could be different with the others. We need to ensure that the storages (and the results) was the same

Consensus nodes only sign and validate the block, but all nodes need to execute all the transactions in his own chain, so if you change the logic, and old transactions now have a different value as before the change, we have a problem

@shargon
Copy link
Copy Markdown
Member

shargon commented May 25, 2018

We need to check the height of the deploy of the contract, for enable or disable this behaviour, or we need versioning

@erikzhang
Copy link
Copy Markdown
Member

I don't think it's necessary.
If the new contract does not support new triggers, they will fail to run under the new trigger, so there is no effect. And if they support new triggers, proving that they want to work properly under new triggers, they should run correctly regardless of whether v2.7.5 is deployed or not. So if they were to be run before the v2.7.5 was deployed to the mainnet, we should correct the result after v2.7.5 is deployed.

@dicarlo2
Copy link
Copy Markdown

After thinking about this some more (while implementing for neo-one's node), I'm starting to wonder if we actually need this functionality. At a high level, NEP-7 seems to attempt to solve two problems (based on this discussion and the proposal text):

  1. Allow smart contracts to refuse UTXO transfers
  2. Allow smart contracts to react to UTXO transfers

Today, the problem is that smart contracts are valid addresses and thus can be used as the address of an output for any transaction, but can only properly react to transfers in an InvocationTransaction. The merged changes here attempt to solve this, but I think it may make things a bit more complex than they really need to be, both from a node implementation and tracking (e.g. an explorer) standpoint as well as from the view of a smart contract. Both VerificationR and ApplicationR are executed on a single output at a time - what happens if a transaction has multiple outputs to the same address? How is the smart contract supposed to handle that correctly? It seems the only way is for the smart contract to check in VerificationR and ApplicationR if there are any other outputs corresponding to itself, which complicates smart contract authoring. Similarly, even in the single output case, authors need to be careful not to mix concerns between Application and ApplicationR. It's also unclear what the use-case is for a smart contract to just receive arbitrary funds without a corresponding method invoke. For example, should an ICO contract no longer implement mintTokens and instead just use the 'receive' event of ApplicationR? This is possible, but it adds to the complexity of authoring smart contracts by increasing the API surface area.

Overall, I'd like to revisit what we're trying to accomplish with this change. If my understanding above is correct, it seems like the simplest solution would be to just make it so that outputs cannot use a smart contract address unless they are an InvocationTransaction. This would allow smart contracts to (almost) guarantee that they can both refuse and react to InvocationTransactions. Now, it's only "almost" because we would also need a way to guarantee that each smart contract that corresponds to an address in the output has actually been executed, i.e. it's not a rogue InvocationTransaction with smart contract address outputs without a corresponding invocation of the smart contract. This turns out to be rather easy to solve though, we can just track which smart contracts are executed as part of the InvocationTransaction's script, and if we haven't seen at least (or perhaps exactly) one execution of the smart contract, then the transaction is rejected.

In summary, an alternative implementation that does not increase the API surface area could be to make the following changes:

  1. Reject all non-invocation transactions whose outputs contain an address that is a smart contract address.
  2. Always run the script of an InvocationTransaction with the Verification trigger if it contains UTXO outputs to the smart contract. (As opposed to today where the transaction must be constructed correctly by the client to run the trigger.)
  3. Track which smart contracts have executed during InvocationTransaction script processing during the Verification trigger to verify that the smart contract address outputs have all had a chance to run. To simplify smart contract implementation, we could require that corresponding smart contracts are executed exactly once in the script. This would eliminate the need to have some sort of tracking of calls to cover the case where the smart contract is invoked multiple times in the script (i.e. don't allow multiple invocations).

The above changes would guarantee that a smart contract can both refuse and react to UTXO transfers. The change would be backwards compatible with the Application trigger because we do not change anything about it, we only start rejecting malformed transactions in the Verification phase. The change would still require coordination with ongoing ICOs when it's rolled out, however it's unlikely to break any ICOs because they invariably all implement the Verification trigger for mintTokens anyways because the primary ICO participation client (NEON) mandates it. Overall, the above changes seem simpler from an implementation perspective on the node, for authoring smart contracts and for external tracking of blockchain state changes.

@erikzhang @localhuman @canesin @shargon @RavenXce thoughts?

@canesin
Copy link
Copy Markdown
Contributor

canesin commented May 26, 2018

@dicarlo2 that is a great suggestion, it is a lot closer to @erikzhang original intent and design for the smart contract system, are you willing to do a NEP for that ? We could ask @shargon and @osmirnov to implement on the reference and move NEP-7 to "Replaced" status that is in NEP-1. I understand that hard work from @RavenXce went into this proposal implementation, but maybe we should have discussed the proposal in itself more.

The only point that I think we should keep is the multiple invoke per transaction, maybe there could be some iteration on how to handle that better from the smart contract side.

@RavenXce
Copy link
Copy Markdown
Contributor Author

RavenXce commented May 27, 2018

what happens if a transaction has multiple outputs to the same address

The trigger is only invoked once per receiving address (smart contract), regardless of outputs.

For example, should an ICO contract no longer implement mintTokens and instead just use the 'receive' event of ApplicationR? This is possible, but it adds to the complexity of authoring smart contracts by increasing the API surface area.

I think it reduces the API surface area as there is only one method used for receiving system assets.

it's not a rogue InvocationTransaction with smart contract address outputs without a corresponding invocation of the smart contract.

I don't get how this matters? If the output is not a deployed contract, or does not comply to NEP-7, nothing actually happens - so it is the same as sending NEO to a random ICO that has ended currently.

  1. Reject all non-invocation transactions whose outputs contain an address that is a smart contract address.

The whole point is that the smart contract author can reject using VerificationR by checking the txn type, if he wants to, or alternatively react to a non-invocation txn. I think this also allows ICOs to be conducted with ContractTransactions. Also remember that since Application runs before ApplicationR, state updates from the manual invocation script can be taken into account too.

  1. Always run the script of an InvocationTransaction with the Verification trigger if it contains UTXO outputs to the smart contract. (As opposed to today where the transaction must be constructed correctly by the client to run the trigger.)

I think the issue with using a same trigger is that then there is no way to differentiate a trigger crafted through the invocation script vs triggered automatically once per transaction - so if it's done this way, smart contract authors then have to count outputs, check that it is not a double count through multiple appcalls, etc. manually, and nothing is solved.

  1. Track which smart contracts have executed during InvocationTransaction script processing during the Verification trigger to verify that the smart contract address outputs have all had a chance to run. To simplify smart contract implementation, we could require that corresponding smart contracts are executed exactly once in the script. This would eliminate the need to have some sort of tracking of calls to cover the case where the smart contract is invoked multiple times in the script (i.e. don't allow multiple invocations).

If any VerificationR trigger is rejected, the txn as a whole is rejected anyway, and does not appear on the blockchain, so there is no need to track anything additional.

@dicarlo2
Copy link
Copy Markdown

dicarlo2 commented May 27, 2018

The trigger is only invoked once per receiving address (smart contract), regardless of outputs.

Ah, I missed that.

I think it reduces the API surface area as there is only one method used for receiving system assets.

What about a contract that needs to support multiple methods for receiving system assets? How are they supposed to use ApplicationR? I suppose they could track which method a transaction corresponds to, but doesn't that bring us back to square one of tracking multiple app calls for a given transaction?

Similarly, even in the single method case, what about a contract that requires arguments to the method for receiving system assets? They again, have to track that there are outputs in the Application trigger to store the arguments corresponding to that transaction to be used in ApplicationR. Even worse, there's no way to store the arguments in the Verification trigger (which also is not even guaranteed to be run) for use in the VerificationR trigger.

I don't get how this matters? If the output is not a deployed contract, or does not comply to NEP-7, nothing actually happens - so it is the same as sending NEO to a random ICO that has ended currently.

The point of this is to enforce correctness at the blockchain level so that it's impossible to accidentally send system assets to a contract that does not react to them, replacing the need for extra triggers.

I think this also allows ICOs to be conducted with ContractTransactions

It does, but what is the advantage of that?

Also remember that since Application runs before ApplicationR, state updates from the manual invocation script can be taken into account too.

What is the advantage of doing this? Does this not also complicate smart contract authoring? If you wanted to take into account state changes from Application in your ApplicationR trigger, you'd again have to do some kind of tracking of the fact that there are outputs in your Application trigger.

I think the issue with using a same trigger is that then there is no way to differentiate a trigger crafted through the invocation script vs triggered automatically once per transaction - so if it's done this way, smart contract authors then have to count outputs, check that it is not a double count through multiple appcalls, etc. manually, and nothing is solved.

Hence why I mentioned we could (and probably should) enforce that a smart contract is called exactly once.

@dicarlo2
Copy link
Copy Markdown

The only point that I think we should keep is the multiple invoke per transaction, maybe there could be some iteration on how to handle that better from the smart contract side.

I think I'd prefer if we were more restrictive to start and then if there is a good use-case for multiple invokes for transactions that send system assets, then and only then do we consider the best way to implement it.

@dicarlo2
Copy link
Copy Markdown

dicarlo2 commented May 27, 2018

At any rate, the current solution is problematic for (at least) two reasons:

  1. VerificationR will break any ongoing ICOs, requiring additional coordination with dapp developers to ensure a smooth rollout.
  2. ApplicationR is not backwards compatible.

Those 2 alone should be enough to consider alternatives.

@RavenXce
Copy link
Copy Markdown
Contributor Author

RavenXce commented May 27, 2018

What about a contract that needs to support multiple methods for receiving system assets? How are they supposed to use ApplicationR?
What is the advantage of doing this? Does this not also complicate smart contract authoring?

I actually agree it would be simpler for most cases if Application is invoked after ApplicationR, which is the original implementation, and I asked about it here: #172 (comment) when it was changed.

The way that I implemented it originally, ApplicationR can just increment a balance associated to the address, and Application can make use of it later on (and clean up by deleting). As it stands now however, to receive and use asset with multiple operation types, Application will have to save the requested operation to storage, or make use of txn attributes. Either that, or contracts can enforce that receiving assets and making use of said assets should be done in two steps (separate txns) - that may actually be the most sane solution if the contract is complex anyway.

But anyway, some sort of tracking has to be done if the SC author wants to use both triggers in tandem. This is no less complicated then how it is currently as double appcalls have to be tracked (regardless of how many operation types there are for receiving assets)

...you'd again have to do some kind of tracking of the fact that there are outputs in your Application trigger.

I do not get this point however. We are talking about the same transaction so they would have the same outputs?

It does, but what is the advantage of that?

I think if we want to keep the API surface low / client requirements simple, as well as reduce errors in sending system assets, this is helps a lot, isn't it? The guarantee as a smart contract author that as long as my contract receives assets, X will be triggered once and only once per txn, is a useful thing to have.

The point of this is to enforce correctness at the blockchain level so that it's impossible to accidentally send system assets to a contract that does not react to them, replacing the need for extra triggers.

This is actually done (again?) by the Payable flag, but I believe it suffers from the same 2 concerns as you mentioned in #172 (comment).

@dicarlo2
Copy link
Copy Markdown

dicarlo2 commented May 27, 2018

As it stands now however, to receive and use asset with multiple operation types, Application will have to save the requested operation to storage, or make use of txn attributes.

Right, so I think we agree that, at least for multiple operations, ApplicationR after Application as it is now doesn't actually help - the same kind of tracking needs to be done, just not against outputs but instead the calls seen.

But anyway, some sort of tracking has to be done if the SC author wants to use both triggers in tandem. This is no less complicated then how it is currently as double appcalls have to be tracked (regardless of how many operation types there are for receiving assets)

The current design also doesn't work at all for multiple app calls or multiple methods in the VerificationR trigger. I.e. same as it is today.

I do not get this point however. We are talking about the same transaction so they would have the same outputs?

What I meant by that is rather than being able to just stick all of your system asset handling code in ApplicationR, you'd likely still need to write code to check and process if there are system assets anyways in Application. For example, consider a method invocation with a set of arguments, the only way to know which method or arguments are used is in Application, so you would likely need to process and/or track (in multiple call case) the outputs anyways in Application calls.

The guarantee as a smart contract author that as long as my contract receives assets, X will be triggered once and only once per txn, is a useful thing to have.

I think we agree on this point, see point 3 on implementation ("we could require that corresponding smart contracts are executed exactly once in the script").

I just wanted to thank @RavenXce for his comments - I think it has helped us all explore the problem a bit more thoroughly. Despite my mistake of thinking ApplicationR could be invoked multiple times per contract, I think there are still many flaws with the current approach. Here's a summary (let me know if I've missed anything):

  1. VerificationR will break any ongoing ICOs, requiring additional coordination with dapp developers to ensure a smooth rollout.
  2. ApplicationR is not backwards compatible.
  3. Multiple invocations of the same contract still require tracking in Application
  4. There is no way to track multiple invocations in Verification for use in VerificationR
  5. A smart contract with multiple methods that expects system assets to be attached still require additional tracking in Application for use in ApplicationR.
  6. There is no way to know which method was called in Verification for use in VerificationR
  7. Even in the single invocation + single method case, there is no way to know what the arguments were to the invocation in VerificationR.
  8. Even if there was a way to track method calls or invocations from Verification -> VerificationR, there is no guarantee that Verification will actually run.
  9. It introduces additional triggers that smart contract authors have to reason about. (Note that this may be justified if there were no other issues)

My proposal is that we instead do the following (mostly copy-pasted from above):

  1. Reject all non-InvocationTransactions whose outputs contain an address that is a smart contract address.
  2. Always run the script of an InvocationTransaction with the Verification trigger if it contains system assets sent to the smart contract.
  3. Track which smart contracts have executed during InvocationTransaction script processing in the Verification trigger to verify that the smart contract address outputs have all had a chance to run.
  4. Require that corresponding smart contracts are executed exactly once in the InvocationTransaction script during the Verification trigger. (We can relax this later if need be)

Here's how it compares for the above issues:

  1. This still has the potential to break ongoing ICOs, however since most if not all implement the Verification trigger due to NEON requiring it, it likely won't affect any ICOs.
  2. It is backwards compatible since we only enforce that a contract is executed exactly once in the Verification phase.
  3. We do not allow multiple invocations.
  4. We do not allow multiple invocations.
  5. Multiple methods are supported.
  6. We know which method was called because we're invoking the script in the Verification trigger.
  7. We know which arguments were used because we're invoking the script in the Verification trigger.
  8. Verification is guaranteed to run in InvocationTransactions with system assets.
  9. We do not introduce additional triggers.

It also contains all of the same properties of the current solution:

  1. Guarantee that Verification is run when a contract receives a system asset, allowing smart contracts to reject the transaction.
  2. Guarantee that Verification/Application are executed exactly once for each smart contract, allowing smart contracts to react to the transaction without the need for extra tracking code.

It also leaves the door open for allowing multiple invocations of the same smart contract when system assets are attached should we decide that it is necessary in the future. If we were to allow this, I believe any solution will require additional tracking, so perhaps that's something we could consider building into NEO directly - for example a special syscall to "mark" an output as consumed and another syscall to fetch all unmarked outputs. Still, whether it's built-in or smart contract authors just use storage to mark, we still have the guarantee that Verification is run and that the smart contract is invoked at least once by the InvocationTransaction script.

@erikzhang
Copy link
Copy Markdown
Member

VerificationR will break any ongoing ICOs, requiring additional coordination with dapp developers to ensure a smooth rollout.

We can deploy NEP-7 with a block height for VerificationR.

There is no way to track multiple invocations in Verification for use in VerificationR.

We can provide a new API to get the number of times the current contract is invoked.

@dicarlo2
Copy link
Copy Markdown

@erikzhang what about the other 7 issues?

We can provide a new API to get the number of times the current contract is invoked.

What if the transaction doesn't cause Verification to be run?

@erikzhang
Copy link
Copy Markdown
Member

what about the other 7 issues?

I think they are not NEP-7 issue in nature.

@dicarlo2
Copy link
Copy Markdown

dicarlo2 commented May 29, 2018

@erikzhang Sure, but what exactly is the point of NEP-7 if it's not taking a holistic approach to solving the issues? We seem to be adding extra triggers and thus extra complexity without really solving anything.

Besides, I'm not seeing how they are not directly related to NEP-7, all of the stated issues are a direct result of NEP-7 and how it's implemented. Can you explain how they are not NEP-7 issues in nature?

@erikzhang
Copy link
Copy Markdown
Member

erikzhang commented May 30, 2018

NEP-7 just added two new triggers and didn't do anything else.

If a contract does not support VerificationR, then it will not be able to receive the transfer, which can be solved by checking the height of the block.

If a contract does not support ApplicationR, then it does not affect anything, so it is backward compatible.

Besides, I don't see any other new problems with NEP-7. If there is, then it must be a pre-existing problem.

@dicarlo2
Copy link
Copy Markdown

@erikzhang Right, these are existing problems, and we're adding new triggers that add to the complexity of smart contract authoring, node implementation and handling among others without really solving anything. Have you considered the alternative solution I posted above?

It's concerning that despite opposition from many key stakeholders - multiple members of City of Zion and NEO Tracker and very little support in general, we're still moving forward with this implementation.

@erikzhang
Copy link
Copy Markdown
Member

erikzhang commented Jun 1, 2018

I've written a new template which using NEP-7 for ICO:

public class Contract1 : SmartContract
{
    private static readonly byte[] owner = "ATrzHaicmhRj15C3Vv6e6gLfLqhSD2PtTr".ToScriptHash();
    private static readonly byte[] neo_asset_id = { 155, 124, 255, 218, 166, 116, 190, 174, 15, 147, 14, 190, 96, 133, 175, 144, 147, 229, 254, 86, 179, 74, 92, 34, 12, 205, 207, 110, 252, 51, 111, 197 };
    private const int ico_start_time = 1506787200;
    private const int ico_end_time = 1538323200;

    public static event Action<byte[], byte[], BigInteger> transfer;

    public static object Main(string operation, object[] args)
    {
        switch (operation)
        {
            case "totalSupply":
                return totalSupply();
            case "name":
                return "ICO_Template_NEP7";
            case "symbol":
                return "ITN";
            case "decimals":
                return 8;
            case "balanceOf":
                return balanceOf((byte[])args[0]);
            case "transfer":
                return _transfer(ExecutionEngine.CallingScriptHash, (byte[])args[0], (byte[])args[1], (BigInteger)args[2]);
            case "receiving":
                return receiving();
            case "received":
                return received();
            case "withdraw":
                return withdraw();
            default:
                throw new InvalidOperationException();
        }
    }

    public static BigInteger totalSupply()
    {
        StorageMap storage = Storage.CurrentContext.CreateMap(Prefixes.Metadata);
        return storage.Get("totalSupply").AsBigInteger();
    }

    public static BigInteger balanceOf(byte[] account)
    {
        if (account.Length != 20) throw new ArgumentException();
        StorageMap storage = Storage.CurrentContext.CreateMap(Prefixes.Balance);
        return storage.Get(account).AsBigInteger();
    }

    private static bool _transfer(byte[] caller, byte[] from, byte[] to, BigInteger amount)
    {
        if (from.Length != 20 || to.Length != 20) throw new ArgumentException();
        if (amount < 0) throw new ArgumentOutOfRangeException();
        StorageMap storage = Storage.CurrentContext.CreateMap(Prefixes.Balance);
        BigInteger balance_from = storage.Get(from).AsBigInteger();
        if (balance_from < amount) return false;
        bool verified;
        if (from == caller)
            verified = true;
        else
            verified = Runtime.CheckWitness(from);
        if (!verified) return false;
        Contract contract_to = Blockchain.GetContract(to);
        if (contract_to != null && !contract_to.IsPayable) return false;
        if (from != to && amount != 0)
        {
            BigInteger balance_to = storage.Get(to).AsBigInteger();
            if (balance_from == amount)
                storage.Delete(from);
            else
                storage.Put(from, balance_from - amount);
            storage.Put(to, balance_to + amount);
        }
        transfer(from, to, amount);
        return true;
    }

    public static bool receiving()
    {
        if (Runtime.Trigger != TriggerType.VerificationR)
            throw new InvalidOperationException();
        uint now = Runtime.Time;
        if (now < ico_start_time || now > ico_end_time)
            return false;
        return true;
    }

    public static bool received()
    {
        if (Runtime.Trigger != TriggerType.ApplicationR)
            throw new InvalidOperationException();
        uint now = Runtime.Time;
        if (now < ico_start_time || now > ico_end_time)
            throw new Exception();
        Transaction tx = (Transaction)ExecutionEngine.ScriptContainer;
        BigInteger amount = 0;
        foreach (TransactionOutput output in tx.GetOutputs())
            if (output.ScriptHash == ExecutionEngine.ExecutingScriptHash && output.AssetId == neo_asset_id)
                amount += output.Value;
        if (amount == 0) throw new Exception();
        byte[] sender = null;
        foreach (TransactionOutput reference in tx.GetReferences())
            if (reference.AssetId == neo_asset_id)
            {
                sender = reference.ScriptHash;
                break;
            }
        StorageMap storage_balance = Storage.CurrentContext.CreateMap(Prefixes.Balance);
        BigInteger balance = storage_balance.Get(sender).AsBigInteger();
        storage_balance.Put(sender, balance + amount);
        StorageMap storage_meta = Storage.CurrentContext.CreateMap(Prefixes.Metadata);
        BigInteger totalSupply = storage_meta.Get("totalSupply").AsBigInteger();
        storage_meta.Put("totalSupply", totalSupply + amount);
        transfer(null, sender, amount);
        return true;
    }

    public static bool withdraw()
    {
        if (Runtime.Trigger != TriggerType.Verification)
            throw new InvalidOperationException();
        return Runtime.CheckWitness(owner);
    }
}

@erikzhang erikzhang mentioned this pull request Jun 9, 2018
lock9 pushed a commit to simplitech/neo that referenced this pull request Aug 3, 2018
Thacryba pushed a commit to simplitech/neo that referenced this pull request Feb 17, 2020
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.

10 participants