|
|
@@ -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
|