From cc42af4c3090da77516ba56ba1fb530ca22c51e3 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Thu, 28 Mar 2024 14:44:53 +0000 Subject: [PATCH] Do not error on state change for transient objects (#229) --- internal/ethapi/api.go | 13 +++++++++++-- suave/e2e/workflow_test.go | 6 +++++- suave/sol/standard_peekers/example.sol | 14 ++++++++++++++ 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 810fff12d..ef9926a17 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1948,6 +1948,7 @@ func (s *TransactionAPI) SendRawTransaction(ctx context.Context, input hexutil.B } type mevmStateLogger struct { + suappAddr common.Address hasStoredState bool } @@ -1955,7 +1956,13 @@ func (m *mevmStateLogger) CaptureStart(env *vm.EVM, from common.Address, to comm } func (m *mevmStateLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { - if op == vm.SSTORE { + if op == vm.SSTORE && scope.Contract.Address() == m.suappAddr { + // Modify the state storage is a nil operation in a Suapp, since the changes are not synced up with the + // consensus state. It is confusing an a bad UX for users that modify the state and nothing happens. + // Thus, we capture the state storage modification and return an error. + // However, we do leverage this volatile state to create temporal Contracts that act as Objects in the traditional sense. + // Only valid during the scope of the Confidential request. Thus, we need to differentiate between the two state changes: + // the ones performed on the Suapp itself (confusing for users) and the ones performed on temporal Contracts. m.hasStoredState = true } } @@ -1995,7 +2002,9 @@ func runMEVM(ctx context.Context, b Backend, state *state.StateDB, header *types return nil, nil, nil, err } - storageAccessTracer := &mevmStateLogger{} + storageAccessTracer := &mevmStateLogger{ + suappAddr: *msg.To, + } blockCtx := core.NewEVMBlockContext(header, NewChainContext(ctx, b), nil) suaveCtx := b.SuaveContext(tx, confidentialRequest) diff --git a/suave/e2e/workflow_test.go b/suave/e2e/workflow_test.go index 9d4f05283..7b2f218bc 100644 --- a/suave/e2e/workflow_test.go +++ b/suave/e2e/workflow_test.go @@ -1168,9 +1168,13 @@ func TestE2EOnChainStateTransition(t *testing.T) { contractAddr := common.Address{0x3} sourceContract := sdk.GetContract(contractAddr, exampleCallSourceContract.Abi, clt) - // a confidential request cannot make a state change + // a confidential request cannot make a state change in the Suapp _, err := sourceContract.SendTransaction("ilegalStateTransition", []interface{}{}, nil) require.Error(t, err) + + // a confidential request can create a new "temporal" contract and change the state there + _, err = sourceContract.SendTransaction("offchainCreateNewContract", []interface{}{}, nil) + require.NoError(t, err) } func TestE2EConsoleLog(t *testing.T) { diff --git a/suave/sol/standard_peekers/example.sol b/suave/sol/standard_peekers/example.sol index 700544c12..bbe8cfca6 100644 --- a/suave/sol/standard_peekers/example.sol +++ b/suave/sol/standard_peekers/example.sol @@ -89,6 +89,20 @@ contract ExampleEthCallSource { function offchain() public pure returns (bytes memory) { return abi.encodeWithSelector(this.onchain.selector); } + + function offchainCreateNewContract() public returns (bytes memory) { + Other o = new Other(); + o.setValue(10); + return abi.encodeWithSelector(this.onchain.selector); + } +} + +contract Other { + uint256 value; + + function setValue(uint256 _value) public { + value = _value; + } } contract ExampleEthCallTarget {