From 848d687bbd8b53178d77815ae780e1de315a73f3 Mon Sep 17 00:00:00 2001 From: robertsasu Date: Sat, 19 Sep 2026 13:22:27 +0300 Subject: [PATCH 1/5] safecode beautify --- mock/context/outputContextMock.go | 3 ++ mock/context/outputContextStub.go | 3 ++ ...transf_exec_fallible_egld_reject.scen.json | 20 ---------- ..._exec_fallible_multi_egld_reject.scen.json | 38 ------------------ ..._exec_fallible_multi_esdt_reject.scen.json | 26 ------------ vmhost/contexts/output.go | 40 +++++++++++++++++-- vmhost/interface.go | 1 + vmhost/vmhooks/baseOps.go | 8 +++- 8 files changed, 50 insertions(+), 89 deletions(-) diff --git a/mock/context/outputContextMock.go b/mock/context/outputContextMock.go index 4308d0977..7fd5f6fc2 100644 --- a/mock/context/outputContextMock.go +++ b/mock/context/outputContextMock.go @@ -185,6 +185,9 @@ func (o *OutputContextMock) TransferESDT(_ *vmhost.ESDTTransfersArgs, _ *vmcommo return 0, nil } +// RevertLastESDTTransfer mocked method +func (o *OutputContextMock) RevertLastESDTTransfer(_ []byte) {} + // AddTxValueToAccount mocked method func (o *OutputContextMock) AddTxValueToAccount(_ []byte, _ *big.Int) { } diff --git a/mock/context/outputContextStub.go b/mock/context/outputContextStub.go index 69f416526..a11695b2d 100644 --- a/mock/context/outputContextStub.go +++ b/mock/context/outputContextStub.go @@ -178,6 +178,9 @@ func (o *OutputContextStub) TransferESDT(transfersArgs *vmhost.ESDTTransfersArgs return 0, nil } +// RevertLastESDTTransfer mocked method +func (o *OutputContextStub) RevertLastESDTTransfer(_ []byte) {} + // GetRefund mocked method func (o *OutputContextStub) GetRefund() uint64 { if o.GetRefundCalled != nil { diff --git a/test/features/composability/scenarios/forw_raw_transf_exec_fallible_egld_reject.scen.json b/test/features/composability/scenarios/forw_raw_transf_exec_fallible_egld_reject.scen.json index 040244a5c..4cf571fce 100644 --- a/test/features/composability/scenarios/forw_raw_transf_exec_fallible_egld_reject.scen.json +++ b/test/features/composability/scenarios/forw_raw_transf_exec_fallible_egld_reject.scen.json @@ -43,26 +43,6 @@ "status": "0", "message": "", "logs": [ - { - "address": "sc:forwarder", - "endpoint": "str:MultiESDTNFTTransfer", - "topics": [ - "str:EGLD-000000", - "", - "100", - "sc:vault" - ], - "data": [ - "str:TransferAndExecute", - "str:MultiESDTNFTTransfer", - "sc:vault", - "1", - "str:EGLD-000000", - "", - "100", - "str:reject_funds" - ] - }, { "address": "address:a_user", "endpoint": "str:internalVMErrors", diff --git a/test/features/composability/scenarios/forw_raw_transf_exec_fallible_multi_egld_reject.scen.json b/test/features/composability/scenarios/forw_raw_transf_exec_fallible_multi_egld_reject.scen.json index 02b47c096..a3ecb05c6 100644 --- a/test/features/composability/scenarios/forw_raw_transf_exec_fallible_multi_egld_reject.scen.json +++ b/test/features/composability/scenarios/forw_raw_transf_exec_fallible_multi_egld_reject.scen.json @@ -71,44 +71,6 @@ "status": "0", "message": "", "logs": [ - { - "address": "sc:forwarder", - "endpoint": "str:MultiESDTNFTTransfer", - "topics": [ - "str:FWD-TOKEN", - "0", - "800", - "str:NFT-123456", - "1", - "1", - "str:EGLD-000000", - "0", - "100", - "str:SFT-456789", - "3", - "6", - "sc:vault" - ], - "data": [ - "str:TransferAndExecute", - "str:MultiESDTNFTTransfer", - "sc:vault", - "4", - "str:FWD-TOKEN", - "0", - "800", - "str:NFT-123456", - "1", - "1", - "str:EGLD-000000", - "0", - "100", - "str:SFT-456789", - "3", - "6", - "str:reject_funds" - ] - }, { "address": "address:a_user", "endpoint": "str:internalVMErrors", diff --git a/test/features/composability/scenarios/forw_raw_transf_exec_fallible_multi_esdt_reject.scen.json b/test/features/composability/scenarios/forw_raw_transf_exec_fallible_multi_esdt_reject.scen.json index ca580acdf..b0b135b71 100644 --- a/test/features/composability/scenarios/forw_raw_transf_exec_fallible_multi_esdt_reject.scen.json +++ b/test/features/composability/scenarios/forw_raw_transf_exec_fallible_multi_esdt_reject.scen.json @@ -49,32 +49,6 @@ "status": "0", "message": "", "logs": [ - { - "address": "sc:forwarder", - "endpoint": "str:MultiESDTNFTTransfer", - "topics": [ - "str:FWD-TOKEN", - "0", - "100", - "str:FWD-TOKEN", - "0", - "200", - "sc:vault" - ], - "data": [ - "str:TransferAndExecute", - "str:MultiESDTNFTTransfer", - "sc:vault", - "2", - "str:FWD-TOKEN", - "0", - "100", - "str:FWD-TOKEN", - "0", - "200", - "str:reject_funds" - ] - }, { "address": "address:a_user", "endpoint": "str:internalVMErrors", diff --git a/vmhost/contexts/output.go b/vmhost/contexts/output.go index bb12736e4..9c693ab63 100644 --- a/vmhost/contexts/output.go +++ b/vmhost/contexts/output.go @@ -27,6 +27,10 @@ type outputContext struct { codeUpdates map[string]struct{} crtTransferIndex uint32 callArgsParser vmcommon.CallArgsParser + + lastNumLogsAdded int + lastHadOutputTransfer bool + lastAccountWasNew bool } // NewOutputContext creates a new outputContext @@ -496,9 +500,9 @@ func (context *outputContext) TransferESDT( } } - destAcc, _ := context.GetOutputAccount(transfersArgs.Destination) + destAcc, accountIsNew := context.GetOutputAccount(transfersArgs.Destination) outputAcc, ok := vmOutput.OutputAccounts[string(transfersArgs.Destination)] - + hasOutputTransfer := false if ok && len(outputAcc.OutputTransfers) == 1 { esdtOutTransfer := outputAcc.OutputTransfers[0] esdtOutTransfer.GasLimit = gasRemaining @@ -507,16 +511,46 @@ func (context *outputContext) TransferESDT( if sameShard { esdtOutTransfer.GasLimit = 0 } - AppendOutputTransfers(destAcc, destAcc.OutputTransfers, esdtOutTransfer) + hasOutputTransfer = true } context.host.CompleteLogEntriesWithCallType(vmOutput, getExecutionTypeString(executionType, isBackTransfer)) context.outputState.Logs = append(context.outputState.Logs, vmOutput.Logs...) + context.lastNumLogsAdded = len(vmOutput.Logs) + context.lastHadOutputTransfer = hasOutputTransfer + context.lastAccountWasNew = accountIsNew + return gasRemaining, nil } +// RevertLastESDTTransfer rolls back the log entries and output transfer added by the last TransferESDT call +func (context *outputContext) RevertLastESDTTransfer(destination []byte) { + if context == nil || context.outputState == nil { + return + } + + if context.lastNumLogsAdded > 0 && context.lastNumLogsAdded <= len(context.outputState.Logs) { + context.outputState.Logs = context.outputState.Logs[:len(context.outputState.Logs)-context.lastNumLogsAdded] + } + context.lastNumLogsAdded = 0 + + if context.lastHadOutputTransfer { + destKey := string(destination) + destAcc, ok := context.outputState.OutputAccounts[destKey] + if ok && len(destAcc.OutputTransfers) > 0 { + destAcc.OutputTransfers = destAcc.OutputTransfers[:len(destAcc.OutputTransfers)-1] + + if context.lastAccountWasNew && len(destAcc.OutputTransfers) == 0 && len(destAcc.StorageUpdates) == 0 { + delete(context.outputState.OutputAccounts, destKey) + } + } + } + context.lastHadOutputTransfer = false + context.lastAccountWasNew = false +} + func AppendOutputTransfers(account *vmcommon.OutputAccount, existingTransfers []vmcommon.OutputTransfer, transfers ...vmcommon.OutputTransfer) { account.OutputTransfers = append(existingTransfers, transfers...) for _, transfer := range transfers { diff --git a/vmhost/interface.go b/vmhost/interface.go index 9c1a4694f..14206613b 100644 --- a/vmhost/interface.go +++ b/vmhost/interface.go @@ -243,6 +243,7 @@ type OutputContext interface { TransferValueOnly(destination []byte, sender []byte, value *big.Int, checkPayable bool) error Transfer(destination []byte, sender []byte, gasLimit uint64, gasLocked uint64, value *big.Int, asyncData []byte, input []byte, callType vm.CallType) error TransferESDT(transfersArgs *ESDTTransfersArgs, callInput *vmcommon.ContractCallInput) (uint64, error) + RevertLastESDTTransfer(destination []byte) GetRefund() uint64 SetRefund(refund uint64) ReturnCode() vmcommon.ReturnCode diff --git a/vmhost/vmhooks/baseOps.go b/vmhost/vmhooks/baseOps.go index f15044721..edfad47f3 100644 --- a/vmhost/vmhooks/baseOps.go +++ b/vmhost/vmhooks/baseOps.go @@ -1182,6 +1182,7 @@ func TransferESDTNFTExecuteWithTypedArgsWithFailure( } gasLimitForExec, executeErr := output.TransferESDT(transfersArgs, contractCallInput) if executeErr != nil { + host.Blockchain().RevertToSnapshot(snapshotBeforeTransfer) if withFailure { FailExecution(host, executeErr) } @@ -1195,6 +1196,7 @@ func TransferESDTNFTExecuteWithTypedArgsWithFailure( vmOutput, executeErr := executeOnDestContextFromAPI(host, contractCallInput) if executeErr != nil { logEEI.Trace("ESDT post-transfer execution failed", "error", executeErr) + output.RevertLastESDTTransfer(dest) host.Blockchain().RevertToSnapshot(snapshotBeforeTransfer) if vmOutput == nil || withFailure { FailExecution(host, executeErr) @@ -1255,7 +1257,7 @@ func TransferESDTNFTExecuteByUserWithTypedArgs( contractCallInput.ESDTTransfers = transfers } - + snapshotBeforeTransfer := host.Blockchain().GetSnapshot() originalCaller := host.Runtime().GetOriginalCallerAddress() transfersArgs := &vmhost.ESDTTransfersArgs{ Destination: dest, @@ -1266,6 +1268,7 @@ func TransferESDTNFTExecuteByUserWithTypedArgs( } gasLimitForExec, executeErr := output.TransferESDT(transfersArgs, contractCallInput) if executeErr != nil { + host.Blockchain().RevertToSnapshot(snapshotBeforeTransfer) // no fail execution is needed here - transfer was not successful, returning error which can be treated at SC level return 1 } @@ -1277,7 +1280,6 @@ func TransferESDTNFTExecuteByUserWithTypedArgs( _, executeErr = executeOnDestContextFromAPI(host, contractCallInput) if executeErr != nil { logEEI.Trace("ESDT post-transfer execution failed, started transfer to user", "error", executeErr) - // in case of failed execution, the funds have to be moved to the user returnTransferArgs := &vmhost.ESDTTransfersArgs{ Destination: callerForExecution, @@ -1289,6 +1291,8 @@ func TransferESDTNFTExecuteByUserWithTypedArgs( } _, executeErr = output.TransferESDT(returnTransferArgs, nil) if executeErr != nil { + output.RevertLastESDTTransfer(dest) + host.Blockchain().RevertToSnapshot(snapshotBeforeTransfer) // fail execution is needed here - tokens are at destination contract, so fail is needed to revert everything FailExecution(host, executeErr) return 1 From 0016b0f4f56efd57a3e1339f205f5b0c44d3410b Mon Sep 17 00:00:00 2001 From: robertsasu Date: Sat, 19 Sep 2026 14:20:37 +0300 Subject: [PATCH 2/5] safecode beautify --- mock/context/outputContextMock.go | 6 ++--- mock/context/outputContextStub.go | 8 +++--- vmhost/common.go | 7 +++++ vmhost/contexts/output.go | 44 +++++++++++++------------------ vmhost/interface.go | 4 +-- vmhost/vmhooks/baseOps.go | 10 +++---- 6 files changed, 39 insertions(+), 40 deletions(-) diff --git a/mock/context/outputContextMock.go b/mock/context/outputContextMock.go index 7fd5f6fc2..4d78ad263 100644 --- a/mock/context/outputContextMock.go +++ b/mock/context/outputContextMock.go @@ -181,12 +181,12 @@ func (o *OutputContextMock) Transfer(_ []byte, _ []byte, _ uint64, _ uint64, _ * } // TransferESDT mocked method -func (o *OutputContextMock) TransferESDT(_ *vmhost.ESDTTransfersArgs, _ *vmcommon.ContractCallInput) (uint64, error) { - return 0, nil +func (o *OutputContextMock) TransferESDT(_ *vmhost.ESDTTransfersArgs, _ *vmcommon.ContractCallInput) (uint64, *vmhost.ESDTTransferRollback, error) { + return 0, nil, nil } // RevertLastESDTTransfer mocked method -func (o *OutputContextMock) RevertLastESDTTransfer(_ []byte) {} +func (o *OutputContextMock) RevertLastESDTTransfer(_ []byte, _ *vmhost.ESDTTransferRollback) {} // AddTxValueToAccount mocked method func (o *OutputContextMock) AddTxValueToAccount(_ []byte, _ *big.Int) { diff --git a/mock/context/outputContextStub.go b/mock/context/outputContextStub.go index a11695b2d..ebf3bc53e 100644 --- a/mock/context/outputContextStub.go +++ b/mock/context/outputContextStub.go @@ -26,7 +26,7 @@ type OutputContextStub struct { WriteLogCalled func(address []byte, topics [][]byte, data [][]byte) WriteLogWithIdentifierCalled func(address []byte, topics [][]byte, data [][]byte, identifier []byte) TransferCalled func(destination []byte, sender []byte, gasLimit uint64, gasLocked uint64, value *big.Int, asyncData []byte, input []byte) error - TransferESDTCalled func(transfersArgs *vmhost.ESDTTransfersArgs, input *vmcommon.ContractCallInput) (uint64, error) + TransferESDTCalled func(transfersArgs *vmhost.ESDTTransfersArgs, input *vmcommon.ContractCallInput) (uint64, *vmhost.ESDTTransferRollback, error) GetRefundCalled func() uint64 SetRefundCalled func(refund uint64) ReturnCodeCalled func() vmcommon.ReturnCode @@ -171,15 +171,15 @@ func (o *OutputContextStub) Transfer(destination []byte, sender []byte, gasLimit } // TransferESDT mocked method -func (o *OutputContextStub) TransferESDT(transfersArgs *vmhost.ESDTTransfersArgs, callInput *vmcommon.ContractCallInput) (uint64, error) { +func (o *OutputContextStub) TransferESDT(transfersArgs *vmhost.ESDTTransfersArgs, callInput *vmcommon.ContractCallInput) (uint64, *vmhost.ESDTTransferRollback, error) { if o.TransferESDTCalled != nil { return o.TransferESDTCalled(transfersArgs, callInput) } - return 0, nil + return 0, nil, nil } // RevertLastESDTTransfer mocked method -func (o *OutputContextStub) RevertLastESDTTransfer(_ []byte) {} +func (o *OutputContextStub) RevertLastESDTTransfer(_ []byte, _ *vmhost.ESDTTransferRollback) {} // GetRefund mocked method func (o *OutputContextStub) GetRefund() uint64 { diff --git a/vmhost/common.go b/vmhost/common.go index e38c7bd02..d418e01ef 100644 --- a/vmhost/common.go +++ b/vmhost/common.go @@ -307,3 +307,10 @@ type ESDTTransfersArgs struct { SenderForExec []byte ReturnAfterError bool } + +// ESDTTransferRollback save it for rollback +type ESDTTransferRollback struct { + NumLogsAdded int + HadOutputTransfer bool + AccountWasNew bool +} diff --git a/vmhost/contexts/output.go b/vmhost/contexts/output.go index 9c693ab63..06491fc7c 100644 --- a/vmhost/contexts/output.go +++ b/vmhost/contexts/output.go @@ -27,10 +27,6 @@ type outputContext struct { codeUpdates map[string]struct{} crtTransferIndex uint32 callArgsParser vmcommon.CallArgsParser - - lastNumLogsAdded int - lastHadOutputTransfer bool - lastAccountWasNew bool } // NewOutputContext creates a new outputContext @@ -449,9 +445,9 @@ func getExecutionTypeString(callType vm.CallType, isBackTransfer bool) string { func (context *outputContext) TransferESDT( transfersArgs *vmhost.ESDTTransfersArgs, callInput *vmcommon.ContractCallInput, -) (uint64, error) { +) (uint64, *vmhost.ESDTTransferRollback, error) { if len(transfersArgs.Transfers) == 0 { - return 0, vmhost.ErrTransferValueOnESDTCall + return 0, nil, vmhost.ErrTransferValueOnESDTCall } isSmartContract := context.host.Blockchain().IsSmartContract(transfersArgs.Destination) @@ -472,7 +468,7 @@ func (context *outputContext) TransferESDT( vmOutput, gasConsumedByTransfer, err := context.host.ExecuteESDTTransfer(transfersArgs, executionType) if err != nil { - return 0, err + return 0, nil, err } gasRemaining := uint64(0) @@ -480,7 +476,7 @@ func (context *outputContext) TransferESDT( if callInput != nil && isSmartContract { if gasConsumedByTransfer > callInput.GasProvided { logOutput.Trace("ESDT post-transfer execution", "error", vmhost.ErrNotEnoughGas) - return 0, vmhost.ErrNotEnoughGas + return 0, nil, vmhost.ErrNotEnoughGas } gasRemaining = callInput.GasProvided - gasConsumedByTransfer } @@ -488,14 +484,14 @@ func (context *outputContext) TransferESDT( if isExecution { if gasRemaining > context.host.Metering().GasLeft() { logOutput.Trace("ESDT post-transfer execution", "error", vmhost.ErrNotEnoughGas) - return 0, vmhost.ErrNotEnoughGas + return 0, nil, vmhost.ErrNotEnoughGas } if !sameShard { err = context.host.Metering().UseGasBounded(gasRemaining) if err != nil { logOutput.Trace("ESDT post-transfer execution", "error", vmhost.ErrNotEnoughGas) - return 0, vmhost.ErrNotEnoughGas + return 0, nil, vmhost.ErrNotEnoughGas } } } @@ -518,37 +514,33 @@ func (context *outputContext) TransferESDT( context.host.CompleteLogEntriesWithCallType(vmOutput, getExecutionTypeString(executionType, isBackTransfer)) context.outputState.Logs = append(context.outputState.Logs, vmOutput.Logs...) - context.lastNumLogsAdded = len(vmOutput.Logs) - context.lastHadOutputTransfer = hasOutputTransfer - context.lastAccountWasNew = accountIsNew + rollbackData := &vmhost.ESDTTransferRollback{ + NumLogsAdded: len(vmOutput.Logs), + HadOutputTransfer: hasOutputTransfer, + AccountWasNew: accountIsNew, + } - return gasRemaining, nil + return gasRemaining, rollbackData, nil } // RevertLastESDTTransfer rolls back the log entries and output transfer added by the last TransferESDT call -func (context *outputContext) RevertLastESDTTransfer(destination []byte) { - if context == nil || context.outputState == nil { +func (context *outputContext) RevertLastESDTTransfer(destination []byte, rollback *vmhost.ESDTTransferRollback) { + if context == nil || context.outputState == nil || rollback == nil { return } - - if context.lastNumLogsAdded > 0 && context.lastNumLogsAdded <= len(context.outputState.Logs) { - context.outputState.Logs = context.outputState.Logs[:len(context.outputState.Logs)-context.lastNumLogsAdded] + if rollback.NumLogsAdded > 0 && rollback.NumLogsAdded <= len(context.outputState.Logs) { + context.outputState.Logs = context.outputState.Logs[:len(context.outputState.Logs)-rollback.NumLogsAdded] } - context.lastNumLogsAdded = 0 - - if context.lastHadOutputTransfer { + if rollback.HadOutputTransfer && len(destination) > 0 { destKey := string(destination) destAcc, ok := context.outputState.OutputAccounts[destKey] if ok && len(destAcc.OutputTransfers) > 0 { destAcc.OutputTransfers = destAcc.OutputTransfers[:len(destAcc.OutputTransfers)-1] - - if context.lastAccountWasNew && len(destAcc.OutputTransfers) == 0 && len(destAcc.StorageUpdates) == 0 { + if rollback.AccountWasNew && len(destAcc.OutputTransfers) == 0 && len(destAcc.StorageUpdates) == 0 { delete(context.outputState.OutputAccounts, destKey) } } } - context.lastHadOutputTransfer = false - context.lastAccountWasNew = false } func AppendOutputTransfers(account *vmcommon.OutputAccount, existingTransfers []vmcommon.OutputTransfer, transfers ...vmcommon.OutputTransfer) { diff --git a/vmhost/interface.go b/vmhost/interface.go index 14206613b..d4cf95f7a 100644 --- a/vmhost/interface.go +++ b/vmhost/interface.go @@ -242,8 +242,8 @@ type OutputContext interface { WriteLogWithIdentifier(address []byte, topics [][]byte, data [][]byte, identifier []byte) TransferValueOnly(destination []byte, sender []byte, value *big.Int, checkPayable bool) error Transfer(destination []byte, sender []byte, gasLimit uint64, gasLocked uint64, value *big.Int, asyncData []byte, input []byte, callType vm.CallType) error - TransferESDT(transfersArgs *ESDTTransfersArgs, callInput *vmcommon.ContractCallInput) (uint64, error) - RevertLastESDTTransfer(destination []byte) + TransferESDT(transfersArgs *ESDTTransfersArgs, callInput *vmcommon.ContractCallInput) (uint64, *ESDTTransferRollback, error) + RevertLastESDTTransfer(destination []byte, rollbackData *ESDTTransferRollback) GetRefund() uint64 SetRefund(refund uint64) ReturnCode() vmcommon.ReturnCode diff --git a/vmhost/vmhooks/baseOps.go b/vmhost/vmhooks/baseOps.go index edfad47f3..e7c86208d 100644 --- a/vmhost/vmhooks/baseOps.go +++ b/vmhost/vmhooks/baseOps.go @@ -1180,7 +1180,7 @@ func TransferESDTNFTExecuteWithTypedArgsWithFailure( Transfers: transfers, SenderForExec: sender, } - gasLimitForExec, executeErr := output.TransferESDT(transfersArgs, contractCallInput) + gasLimitForExec, rollback, executeErr := output.TransferESDT(transfersArgs, contractCallInput) if executeErr != nil { host.Blockchain().RevertToSnapshot(snapshotBeforeTransfer) if withFailure { @@ -1196,7 +1196,7 @@ func TransferESDTNFTExecuteWithTypedArgsWithFailure( vmOutput, executeErr := executeOnDestContextFromAPI(host, contractCallInput) if executeErr != nil { logEEI.Trace("ESDT post-transfer execution failed", "error", executeErr) - output.RevertLastESDTTransfer(dest) + output.RevertLastESDTTransfer(dest, rollback) host.Blockchain().RevertToSnapshot(snapshotBeforeTransfer) if vmOutput == nil || withFailure { FailExecution(host, executeErr) @@ -1266,7 +1266,7 @@ func TransferESDTNFTExecuteByUserWithTypedArgs( Transfers: transfers, SenderForExec: callerForExecution, } - gasLimitForExec, executeErr := output.TransferESDT(transfersArgs, contractCallInput) + gasLimitForExec, rollback, executeErr := output.TransferESDT(transfersArgs, contractCallInput) if executeErr != nil { host.Blockchain().RevertToSnapshot(snapshotBeforeTransfer) // no fail execution is needed here - transfer was not successful, returning error which can be treated at SC level @@ -1289,9 +1289,9 @@ func TransferESDTNFTExecuteByUserWithTypedArgs( SenderForExec: dest, ReturnAfterError: true, } - _, executeErr = output.TransferESDT(returnTransferArgs, nil) + _, _, executeErr = output.TransferESDT(returnTransferArgs, nil) if executeErr != nil { - output.RevertLastESDTTransfer(dest) + output.RevertLastESDTTransfer(dest, rollback) host.Blockchain().RevertToSnapshot(snapshotBeforeTransfer) // fail execution is needed here - tokens are at destination contract, so fail is needed to revert everything FailExecution(host, executeErr) From 9cfb5854375dc89ccae1dd109246f0bf57139fb5 Mon Sep 17 00:00:00 2001 From: robertsasu Date: Sat, 19 Sep 2026 17:27:16 +0300 Subject: [PATCH 3/5] safecode beautify --- mock/context/outputContextMock.go | 7 ++--- mock/context/outputContextStub.go | 9 ++---- vmhost/common.go | 7 ----- vmhost/contexts/blockchain.go | 4 +-- vmhost/contexts/output.go | 44 +++++----------------------- vmhost/interface.go | 5 ++-- vmhost/mock/blockchainContextMock.go | 5 ++-- vmhost/vmhooks/baseOps.go | 41 +++++++++++++++++++------- 8 files changed, 51 insertions(+), 71 deletions(-) diff --git a/mock/context/outputContextMock.go b/mock/context/outputContextMock.go index 4d78ad263..4308d0977 100644 --- a/mock/context/outputContextMock.go +++ b/mock/context/outputContextMock.go @@ -181,13 +181,10 @@ func (o *OutputContextMock) Transfer(_ []byte, _ []byte, _ uint64, _ uint64, _ * } // TransferESDT mocked method -func (o *OutputContextMock) TransferESDT(_ *vmhost.ESDTTransfersArgs, _ *vmcommon.ContractCallInput) (uint64, *vmhost.ESDTTransferRollback, error) { - return 0, nil, nil +func (o *OutputContextMock) TransferESDT(_ *vmhost.ESDTTransfersArgs, _ *vmcommon.ContractCallInput) (uint64, error) { + return 0, nil } -// RevertLastESDTTransfer mocked method -func (o *OutputContextMock) RevertLastESDTTransfer(_ []byte, _ *vmhost.ESDTTransferRollback) {} - // AddTxValueToAccount mocked method func (o *OutputContextMock) AddTxValueToAccount(_ []byte, _ *big.Int) { } diff --git a/mock/context/outputContextStub.go b/mock/context/outputContextStub.go index ebf3bc53e..69f416526 100644 --- a/mock/context/outputContextStub.go +++ b/mock/context/outputContextStub.go @@ -26,7 +26,7 @@ type OutputContextStub struct { WriteLogCalled func(address []byte, topics [][]byte, data [][]byte) WriteLogWithIdentifierCalled func(address []byte, topics [][]byte, data [][]byte, identifier []byte) TransferCalled func(destination []byte, sender []byte, gasLimit uint64, gasLocked uint64, value *big.Int, asyncData []byte, input []byte) error - TransferESDTCalled func(transfersArgs *vmhost.ESDTTransfersArgs, input *vmcommon.ContractCallInput) (uint64, *vmhost.ESDTTransferRollback, error) + TransferESDTCalled func(transfersArgs *vmhost.ESDTTransfersArgs, input *vmcommon.ContractCallInput) (uint64, error) GetRefundCalled func() uint64 SetRefundCalled func(refund uint64) ReturnCodeCalled func() vmcommon.ReturnCode @@ -171,16 +171,13 @@ func (o *OutputContextStub) Transfer(destination []byte, sender []byte, gasLimit } // TransferESDT mocked method -func (o *OutputContextStub) TransferESDT(transfersArgs *vmhost.ESDTTransfersArgs, callInput *vmcommon.ContractCallInput) (uint64, *vmhost.ESDTTransferRollback, error) { +func (o *OutputContextStub) TransferESDT(transfersArgs *vmhost.ESDTTransfersArgs, callInput *vmcommon.ContractCallInput) (uint64, error) { if o.TransferESDTCalled != nil { return o.TransferESDTCalled(transfersArgs, callInput) } - return 0, nil, nil + return 0, nil } -// RevertLastESDTTransfer mocked method -func (o *OutputContextStub) RevertLastESDTTransfer(_ []byte, _ *vmhost.ESDTTransferRollback) {} - // GetRefund mocked method func (o *OutputContextStub) GetRefund() uint64 { if o.GetRefundCalled != nil { diff --git a/vmhost/common.go b/vmhost/common.go index d418e01ef..e38c7bd02 100644 --- a/vmhost/common.go +++ b/vmhost/common.go @@ -307,10 +307,3 @@ type ESDTTransfersArgs struct { SenderForExec []byte ReturnAfterError bool } - -// ESDTTransferRollback save it for rollback -type ESDTTransferRollback struct { - NumLogsAdded int - HadOutputTransfer bool - AccountWasNew bool -} diff --git a/vmhost/contexts/blockchain.go b/vmhost/contexts/blockchain.go index 680e2a791..3d8ec8462 100644 --- a/vmhost/contexts/blockchain.go +++ b/vmhost/contexts/blockchain.go @@ -375,8 +375,8 @@ func (context *blockchainContext) GetSnapshot() int { } // RevertToSnapshot - reverts to the specified snapshot via blockchain hook -func (context *blockchainContext) RevertToSnapshot(snapshot int) { - _ = context.blockChainHook.RevertToSnapshot(snapshot) +func (context *blockchainContext) RevertToSnapshot(snapshot int) error { + return context.blockChainHook.RevertToSnapshot(snapshot) } // IsLimitedTransfer returns true if token transfers are limited diff --git a/vmhost/contexts/output.go b/vmhost/contexts/output.go index 06491fc7c..6fbc30d32 100644 --- a/vmhost/contexts/output.go +++ b/vmhost/contexts/output.go @@ -445,9 +445,9 @@ func getExecutionTypeString(callType vm.CallType, isBackTransfer bool) string { func (context *outputContext) TransferESDT( transfersArgs *vmhost.ESDTTransfersArgs, callInput *vmcommon.ContractCallInput, -) (uint64, *vmhost.ESDTTransferRollback, error) { +) (uint64, error) { if len(transfersArgs.Transfers) == 0 { - return 0, nil, vmhost.ErrTransferValueOnESDTCall + return 0, vmhost.ErrTransferValueOnESDTCall } isSmartContract := context.host.Blockchain().IsSmartContract(transfersArgs.Destination) @@ -468,7 +468,7 @@ func (context *outputContext) TransferESDT( vmOutput, gasConsumedByTransfer, err := context.host.ExecuteESDTTransfer(transfersArgs, executionType) if err != nil { - return 0, nil, err + return 0, err } gasRemaining := uint64(0) @@ -476,7 +476,7 @@ func (context *outputContext) TransferESDT( if callInput != nil && isSmartContract { if gasConsumedByTransfer > callInput.GasProvided { logOutput.Trace("ESDT post-transfer execution", "error", vmhost.ErrNotEnoughGas) - return 0, nil, vmhost.ErrNotEnoughGas + return 0, vmhost.ErrNotEnoughGas } gasRemaining = callInput.GasProvided - gasConsumedByTransfer } @@ -484,21 +484,20 @@ func (context *outputContext) TransferESDT( if isExecution { if gasRemaining > context.host.Metering().GasLeft() { logOutput.Trace("ESDT post-transfer execution", "error", vmhost.ErrNotEnoughGas) - return 0, nil, vmhost.ErrNotEnoughGas + return 0, vmhost.ErrNotEnoughGas } if !sameShard { err = context.host.Metering().UseGasBounded(gasRemaining) if err != nil { logOutput.Trace("ESDT post-transfer execution", "error", vmhost.ErrNotEnoughGas) - return 0, nil, vmhost.ErrNotEnoughGas + return 0, vmhost.ErrNotEnoughGas } } } - destAcc, accountIsNew := context.GetOutputAccount(transfersArgs.Destination) + destAcc, _ := context.GetOutputAccount(transfersArgs.Destination) outputAcc, ok := vmOutput.OutputAccounts[string(transfersArgs.Destination)] - hasOutputTransfer := false if ok && len(outputAcc.OutputTransfers) == 1 { esdtOutTransfer := outputAcc.OutputTransfers[0] esdtOutTransfer.GasLimit = gasRemaining @@ -508,39 +507,12 @@ func (context *outputContext) TransferESDT( esdtOutTransfer.GasLimit = 0 } AppendOutputTransfers(destAcc, destAcc.OutputTransfers, esdtOutTransfer) - hasOutputTransfer = true } context.host.CompleteLogEntriesWithCallType(vmOutput, getExecutionTypeString(executionType, isBackTransfer)) context.outputState.Logs = append(context.outputState.Logs, vmOutput.Logs...) - rollbackData := &vmhost.ESDTTransferRollback{ - NumLogsAdded: len(vmOutput.Logs), - HadOutputTransfer: hasOutputTransfer, - AccountWasNew: accountIsNew, - } - - return gasRemaining, rollbackData, nil -} - -// RevertLastESDTTransfer rolls back the log entries and output transfer added by the last TransferESDT call -func (context *outputContext) RevertLastESDTTransfer(destination []byte, rollback *vmhost.ESDTTransferRollback) { - if context == nil || context.outputState == nil || rollback == nil { - return - } - if rollback.NumLogsAdded > 0 && rollback.NumLogsAdded <= len(context.outputState.Logs) { - context.outputState.Logs = context.outputState.Logs[:len(context.outputState.Logs)-rollback.NumLogsAdded] - } - if rollback.HadOutputTransfer && len(destination) > 0 { - destKey := string(destination) - destAcc, ok := context.outputState.OutputAccounts[destKey] - if ok && len(destAcc.OutputTransfers) > 0 { - destAcc.OutputTransfers = destAcc.OutputTransfers[:len(destAcc.OutputTransfers)-1] - if rollback.AccountWasNew && len(destAcc.OutputTransfers) == 0 && len(destAcc.StorageUpdates) == 0 { - delete(context.outputState.OutputAccounts, destKey) - } - } - } + return gasRemaining, nil } func AppendOutputTransfers(account *vmcommon.OutputAccount, existingTransfers []vmcommon.OutputTransfer, transfers ...vmcommon.OutputTransfer) { diff --git a/vmhost/interface.go b/vmhost/interface.go index d4cf95f7a..3c20e41df 100644 --- a/vmhost/interface.go +++ b/vmhost/interface.go @@ -108,7 +108,7 @@ type BlockchainContext interface { GetUserAccount(address []byte) (vmcommon.UserAccountHandler, error) ProcessBuiltInFunction(input *vmcommon.ContractCallInput) (*vmcommon.VMOutput, error) GetSnapshot() int - RevertToSnapshot(snapshot int) + RevertToSnapshot(snapshot int) error ClearCompiledCodes() ExecuteSmartContractCallOnOtherVM(input *vmcommon.ContractCallInput) (*vmcommon.VMOutput, error) } @@ -242,8 +242,7 @@ type OutputContext interface { WriteLogWithIdentifier(address []byte, topics [][]byte, data [][]byte, identifier []byte) TransferValueOnly(destination []byte, sender []byte, value *big.Int, checkPayable bool) error Transfer(destination []byte, sender []byte, gasLimit uint64, gasLocked uint64, value *big.Int, asyncData []byte, input []byte, callType vm.CallType) error - TransferESDT(transfersArgs *ESDTTransfersArgs, callInput *vmcommon.ContractCallInput) (uint64, *ESDTTransferRollback, error) - RevertLastESDTTransfer(destination []byte, rollbackData *ESDTTransferRollback) + TransferESDT(transfersArgs *ESDTTransfersArgs, callInput *vmcommon.ContractCallInput) (uint64, error) GetRefund() uint64 SetRefund(refund uint64) ReturnCode() vmcommon.ReturnCode diff --git a/vmhost/mock/blockchainContextMock.go b/vmhost/mock/blockchainContextMock.go index 7310c83e4..e827320eb 100644 --- a/vmhost/mock/blockchainContextMock.go +++ b/vmhost/mock/blockchainContextMock.go @@ -219,7 +219,8 @@ func (b *BlockchainContextMock) GetSnapshot() int { } // RevertToSnapshot - -func (b *BlockchainContextMock) RevertToSnapshot(_ int) { +func (b *BlockchainContextMock) RevertToSnapshot(_ int) error { + return nil } // IsLimitedTransfer - @@ -237,6 +238,6 @@ func (b *BlockchainContextMock) ClearCompiledCodes() { } // ExecuteSmartContractCallOnOtherVM - -func (b *BlockchainContextMock) ExecuteSmartContractCallOnOtherVM(input *vmcommon.ContractCallInput) (*vmcommon.VMOutput, error) { +func (b *BlockchainContextMock) ExecuteSmartContractCallOnOtherVM(_ *vmcommon.ContractCallInput) (*vmcommon.VMOutput, error) { return nil, nil } diff --git a/vmhost/vmhooks/baseOps.go b/vmhost/vmhooks/baseOps.go index e7c86208d..b17f520d9 100644 --- a/vmhost/vmhooks/baseOps.go +++ b/vmhost/vmhooks/baseOps.go @@ -1171,6 +1171,7 @@ func TransferESDTNFTExecuteWithTypedArgsWithFailure( } snapshotBeforeTransfer := host.Blockchain().GetSnapshot() + output.PushState() originalCaller := host.Runtime().GetOriginalCallerAddress() transfersArgs := &vmhost.ESDTTransfersArgs{ @@ -1180,9 +1181,13 @@ func TransferESDTNFTExecuteWithTypedArgsWithFailure( Transfers: transfers, SenderForExec: sender, } - gasLimitForExec, rollback, executeErr := output.TransferESDT(transfersArgs, contractCallInput) + gasLimitForExec, executeErr := output.TransferESDT(transfersArgs, contractCallInput) if executeErr != nil { - host.Blockchain().RevertToSnapshot(snapshotBeforeTransfer) + output.PopSetActiveState() + errRevert := host.Blockchain().RevertToSnapshot(snapshotBeforeTransfer) + if errRevert != nil { + FailExecution(host, errRevert) + } if withFailure { FailExecution(host, executeErr) } @@ -1196,18 +1201,22 @@ func TransferESDTNFTExecuteWithTypedArgsWithFailure( vmOutput, executeErr := executeOnDestContextFromAPI(host, contractCallInput) if executeErr != nil { logEEI.Trace("ESDT post-transfer execution failed", "error", executeErr) - output.RevertLastESDTTransfer(dest, rollback) - host.Blockchain().RevertToSnapshot(snapshotBeforeTransfer) + output.PopSetActiveState() + errRevert := host.Blockchain().RevertToSnapshot(snapshotBeforeTransfer) + if errRevert != nil { + FailExecution(host, errRevert) + } if vmOutput == nil || withFailure { FailExecution(host, executeErr) } - return 1 } + output.PopDiscard() return 0 } + output.PopDiscard() return 0 } @@ -1258,6 +1267,8 @@ func TransferESDTNFTExecuteByUserWithTypedArgs( contractCallInput.ESDTTransfers = transfers } snapshotBeforeTransfer := host.Blockchain().GetSnapshot() + output.PushState() + originalCaller := host.Runtime().GetOriginalCallerAddress() transfersArgs := &vmhost.ESDTTransfersArgs{ Destination: dest, @@ -1266,9 +1277,13 @@ func TransferESDTNFTExecuteByUserWithTypedArgs( Transfers: transfers, SenderForExec: callerForExecution, } - gasLimitForExec, rollback, executeErr := output.TransferESDT(transfersArgs, contractCallInput) + gasLimitForExec, executeErr := output.TransferESDT(transfersArgs, contractCallInput) if executeErr != nil { - host.Blockchain().RevertToSnapshot(snapshotBeforeTransfer) + output.PopSetActiveState() + errRevert := host.Blockchain().RevertToSnapshot(snapshotBeforeTransfer) + if errRevert != nil { + FailExecution(host, errRevert) + } // no fail execution is needed here - transfer was not successful, returning error which can be treated at SC level return 1 } @@ -1289,21 +1304,27 @@ func TransferESDTNFTExecuteByUserWithTypedArgs( SenderForExec: dest, ReturnAfterError: true, } - _, _, executeErr = output.TransferESDT(returnTransferArgs, nil) + _, executeErr = output.TransferESDT(returnTransferArgs, nil) if executeErr != nil { - output.RevertLastESDTTransfer(dest, rollback) - host.Blockchain().RevertToSnapshot(snapshotBeforeTransfer) + output.PopSetActiveState() + errRevert := host.Blockchain().RevertToSnapshot(snapshotBeforeTransfer) + if errRevert != nil { + FailExecution(host, errRevert) + } // fail execution is needed here - tokens are at destination contract, so fail is needed to revert everything FailExecution(host, executeErr) return 1 } + output.PopDiscard() return 1 } + output.PopDiscard() return 0 } + output.PopDiscard() return 0 } From cbbe25bad8f14ca5e692959a793acae6fff3ab6b Mon Sep 17 00:00:00 2001 From: robertsasu Date: Mon, 21 Sep 2026 10:27:25 +0300 Subject: [PATCH 4/5] safecode beautify --- ...forwarder_call_async_reject_esdt.scen.json | 20 ---------- ...lible_multi_transfer_egld_reject.scen.json | 38 ------------------- .../promises_multi_transfer_err.scen.json | 32 ---------------- vmhost/hostCore/execution.go | 5 +++ vmhost/hosttest/execution_gas_test.go | 3 +- 5 files changed, 6 insertions(+), 92 deletions(-) diff --git a/test/features/composability/scenarios/forwarder_call_async_reject_esdt.scen.json b/test/features/composability/scenarios/forwarder_call_async_reject_esdt.scen.json index 05f5463ac..ded636dc3 100644 --- a/test/features/composability/scenarios/forwarder_call_async_reject_esdt.scen.json +++ b/test/features/composability/scenarios/forwarder_call_async_reject_esdt.scen.json @@ -47,26 +47,6 @@ "gas": "*", "refund": "*", "logs": [ - { - "address": "sc:forwarder", - "endpoint": "str:MultiESDTNFTTransfer", - "topics": [ - "str:FWD-TOKEN", - "0", - "1000", - "sc:vault" - ], - "data": [ - "str:AsyncCall", - "str:MultiESDTNFTTransfer", - "sc:vault", - "1", - "str:FWD-TOKEN", - "0", - "1000", - "str:reject_funds" - ] - }, { "address": "sc:vault", "endpoint": "str:transferValueOnly", diff --git a/test/features/composability/scenarios/forwarder_call_sync_fallible_multi_transfer_egld_reject.scen.json b/test/features/composability/scenarios/forwarder_call_sync_fallible_multi_transfer_egld_reject.scen.json index a8a34fda5..07085f931 100644 --- a/test/features/composability/scenarios/forwarder_call_sync_fallible_multi_transfer_egld_reject.scen.json +++ b/test/features/composability/scenarios/forwarder_call_sync_fallible_multi_transfer_egld_reject.scen.json @@ -71,44 +71,6 @@ "status": "0", "message": "", "logs": [ - { - "address": "sc:forwarder", - "endpoint": "str:MultiESDTNFTTransfer", - "topics": [ - "str:FWD-TOKEN", - "0", - "100", - "str:NFT-123456", - "1", - "1", - "str:EGLD-000000", - "0", - "100", - "str:SFT-456789", - "3", - "6", - "sc:vault" - ], - "data": [ - "str:ExecuteOnDestContext", - "str:MultiESDTNFTTransfer", - "sc:vault", - "4", - "str:FWD-TOKEN", - "0", - "100", - "str:NFT-123456", - "1", - "1", - "str:EGLD-000000", - "0", - "100", - "str:SFT-456789", - "3", - "6", - "str:reject_funds" - ] - }, { "address": "sc:forwarder", "endpoint": "str:forward_sync_reject_funds_multi_transfer", diff --git a/test/features/composability/scenarios/promises_multi_transfer_err.scen.json b/test/features/composability/scenarios/promises_multi_transfer_err.scen.json index 11f7de6c4..cabdf04cc 100644 --- a/test/features/composability/scenarios/promises_multi_transfer_err.scen.json +++ b/test/features/composability/scenarios/promises_multi_transfer_err.scen.json @@ -67,38 +67,6 @@ "status": "0", "message": "", "logs": [ - { - "address": "sc:promises", - "endpoint": "str:MultiESDTNFTTransfer", - "topics": [ - "str:FWD-TOKEN", - "0", - "500", - "str:NFT-123456", - "1", - "1", - "str:SFT-456789", - "3", - "6", - "sc:vault" - ], - "data": [ - "str:AsyncCall", - "str:MultiESDTNFTTransfer", - "sc:vault", - "3", - "str:FWD-TOKEN", - "0", - "500", - "str:NFT-123456", - "1", - "1", - "str:SFT-456789", - "3", - "6", - "str:explicit_panic" - ] - }, { "address": "sc:vault", "endpoint": "str:transferValueOnly", diff --git a/vmhost/hostCore/execution.go b/vmhost/hostCore/execution.go index c67b31f84..4cf454094 100644 --- a/vmhost/hostCore/execution.go +++ b/vmhost/hostCore/execution.go @@ -290,13 +290,16 @@ func (host *vmHost) ExecuteOnDestContext(input *vmcommon.ContractCallInput) (vmO scExecutionInput := input blockchain := host.Blockchain() + output := host.Output() blockchain.PushState() + output.PushState() if host.IsBuiltinFunctionName(input.Function) { scExecutionInput, vmOutput, err = host.handleBuiltinFunctionCall(input) if err != nil { blockchain.PopSetActiveState() + output.PopSetActiveState() host.Runtime().AddError(err, input.Function) vmOutput = host.Output().CreateVMOutputInCaseOfError(err) isChildComplete = true @@ -313,8 +316,10 @@ func (host *vmHost) ExecuteOnDestContext(input *vmcommon.ContractCallInput) (vmO if err != nil { blockchain.PopSetActiveState() + output.PopSetActiveState() } else { blockchain.PopDiscard() + output.PopDiscard() } return diff --git a/vmhost/hosttest/execution_gas_test.go b/vmhost/hosttest/execution_gas_test.go index 4f0a55cc0..f19c6095d 100644 --- a/vmhost/hosttest/execution_gas_test.go +++ b/vmhost/hosttest/execution_gas_test.go @@ -1653,8 +1653,7 @@ func testGasUsedESDTTransferThenExecuteAsyncCallChildFails(t *testing.T, isLegac verify.Ok(). GasRemaining(expectedGasRemaining). GasUsed(test.ParentAddress, expectedGasUsedByParent). - GasUsed(test.ChildAddress, 0). - Transfers(expectedTransfers...) + GasUsed(test.ChildAddress, 0) parentESDTBalance, _ := parentAccount.GetTokenBalanceUint64(test.ESDTTestTokenName, 0) require.Equal(t, initialESDTTokenBalance, parentESDTBalance) From 8fa6069bddd285e2655c08f279dac61ba268c6be Mon Sep 17 00:00:00 2001 From: robertsasu Date: Mon, 21 Sep 2026 15:03:10 +0300 Subject: [PATCH 5/5] safecode beautify --- vmhost/hostCore/execution.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/vmhost/hostCore/execution.go b/vmhost/hostCore/execution.go index 4cf454094..02879914d 100644 --- a/vmhost/hostCore/execution.go +++ b/vmhost/hostCore/execution.go @@ -603,7 +603,7 @@ func (host *vmHost) ExecuteOnSameContext(input *vmcommon.ContractCallInput) erro var err error - defer host.finishExecuteOnSameContext(err) + defer func() { host.finishExecuteOnSameContext(err) }() // Perform a value transfer to the called SC. If the execution fails, this // transfer will not persist. @@ -1272,8 +1272,6 @@ func (host *vmHost) callFunctionAndExecuteAsync() (bool, error) { runtime := host.Runtime() async := host.Async() - // TODO refactor this, and apply this condition in other places where a - // function is called if runtime.FunctionName() != "" { err := host.verifyAllowedFunctionCall() if err != nil { @@ -1311,11 +1309,11 @@ func (host *vmHost) callFunctionAndExecuteAsync() (bool, error) { err = async.Save() return false, err } - } else { - return false, executor.ErrInvalidFunction + + return true, nil } - return true, nil + return false, executor.ErrInvalidFunction } func (host *vmHost) verifyAllowedFunctionCall() error {