فهرست منبع

book/sc: include info from discussion about state changes when invoke() is called

x 2 سال پیش
والد
کامیت
fe43e2a923
1فایلهای تغییر یافته به همراه103 افزوده شده و 1 حذف شده
  1. 103 1
      doc/src/arch/sc/sc.md

+ 103 - 1
doc/src/arch/sc/sc.md

@@ -46,9 +46,11 @@ Let `n = len(children)`. Then inside the contract, we are expected to
 call `invoke()` exactly `n` times.
 
 ```rust
-    let call = invoke(function_id);
+    let (params, retdat) = invoke(function_id);
 ```
 
+This doesn't actually invoke any function directly, but just iterates to the
+next child call in the current call. We should iterate through the entire list.
 If this doesn't happen, then there's a mismatch and the call fails with an
 error.
 
@@ -64,6 +66,106 @@ Essentially the entire program trace is created ahead of time by the "prover",
 and then the verifier simply checks the trace for correctness. This can be
 done in parallel since we have all the data ahead of time.
 
+### Depending on State Changes
+
+Another downside of this model is that state changes at the site of invocation
+are not immediate.
+
+Currently in DarkFi, we separate contract calls into 2-phases: `process()`
+which verifies the calldata, and `update()` which takes a state update from
+`process()` and writes the changes.
+
+Host functions have permissions:
+
+* `process()` is READONLY, which means state can only be read. For example
+  it can use `db_get()` but *not* `db_set()`.
+* `update()` is WRITEONLY. It can only write to the state. For example
+  it can use `db_set()` but *not* `db_get()`.
+
+Let `A`, `B` be smart contract functions. `A` calls `invoke(B)`. The normal
+flow in Ethereum would be:
+
+```
+process(A) ->
+    invoke(B) ->
+        process(B) ->
+        update(B) ->
+update(A)
+```
+
+However with the model described, instead would be:
+
+```
+process(A) ->
+invoke(B) ->
+    process(B) ->
+update(A)
+update(B)
+```
+
+State changes occur linearly after all `process()` calls have passed
+successfully.
+
+An upside of this strict separation, is that it makes reentrancy attacks
+impossible. Say for example we have this code:
+
+```js
+contract VulnerableContract {
+    function withdraw(amount) {
+        // ...
+        sender.call(amount);
+        balances[sender] -= amount;
+    }
+}
+
+contract AttackerContract {
+    function receive(amount) {
+        if balance(VulnerableContract) >= 1 {
+            VulnerableContract.withdraw(1);
+        }
+    }
+}
+```
+
+The main recommended way to mitigate these attacks is using the
+'checks-effects-interactions' pattern[[1]](https://docs.soliditylang.org/en/v0.4.21/security-considerations.html#use-the-checks-effects-interactions-pattern)
+[[2]](https://fravoll.github.io/solidity-patterns/checks_effects_interactions.html).
+whereby the code is delineated into 3 strict parts.
+
+```js
+contract ChecksEffectsInteractions {
+    // ...
+
+    function withdraw(uint amount) public {
+        require(balances[msg.sender] >= amount);
+
+        balances[msg.sender] -= amount;
+
+        msg.sender.transfer(amount);
+    }
+}
+```
+
+Interactions always occur last since they cause unknown effects. Performing
+logic based off state changes from an interacting outside contract (especially
+when user provided) is very risky.
+
+With the model of `invoke()` given above, we do not have any possibility of such
+an attack occurring.
+
+### Communication Between Contracts
+
+Given the above discussion, how can we then have an interaction which performs
+some action which the parent caller may depend on?
+
+One way is that `process()` which produces a `StateUpdate` for `update()` could
+also return arbitrary data which is usable by the parent caller. This arbitrary
+data could include info on the state change (such as `StateUpdate` directly) or
+info on the execution path that was performed.
+
+The parent caller can then use this information in lieu of a directly state
+change.
+
 ## ABI
 
 We can do this in Rust through clever use of the serializer. Basically there