Skip to content
Draft
9 changes: 9 additions & 0 deletions builtInFunctions/creator.go
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,15 @@ func (b *builtInFuncCreator) CreateBuiltInFunctionContainer() error {
return err
}

newFunc, err = NewMigrateCodeLeafFunc(b.gasConfig.BuiltInCost.MigrateCodeLeaf, b.enableEpochsHandler, b.accounts)
if err != nil {
return err
}
err = b.builtInFunctions.Add(core.BuiltInFunctionMigrateCodeLeaf, newFunc)
if err != nil {
return err
}

return nil
}

Expand Down
3 changes: 2 additions & 1 deletion builtInFunctions/creator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ func fillGasMapBuiltInCosts(value uint64) map[string]uint64 {
gasMap["UnGuardAccount"] = value
gasMap["TrieLoadPerNode"] = value
gasMap["TrieStorePerNode"] = value
gasMap["MigrateCodeLeaf"] = value

return gasMap
}
Expand Down Expand Up @@ -150,7 +151,7 @@ func TestCreateBuiltInContainer_Create(t *testing.T) {

err := f.CreateBuiltInFunctionContainer()
assert.Nil(t, err)
assert.Equal(t, 36, f.BuiltInFunctionContainer().Len())
assert.Equal(t, 37, f.BuiltInFunctionContainer().Len())

err = f.SetPayableHandler(nil)
assert.NotNil(t, err)
Expand Down
2 changes: 2 additions & 0 deletions builtInFunctions/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const (
ScToScLogEventFlag core.EnableEpochFlag = "ScToScLogEventFlag"
FixGasRemainingForSaveKeyValueFlag core.EnableEpochFlag = "FixGasRemainingForSaveKeyValueFlag"
IsChangeOwnerAddressCrossShardThroughSCFlag core.EnableEpochFlag = "IsChangeOwnerAddressCrossShardThroughSCFlag"
MigrateCodeLeafFlag core.EnableEpochFlag = "MigrateCodeLeafFlag"
)

// allFlags must have all flags used by mx-chain-vm-common-go in the current version
Expand All @@ -51,4 +52,5 @@ var allFlags = []core.EnableEpochFlag{
ScToScLogEventFlag,
FixGasRemainingForSaveKeyValueFlag,
IsChangeOwnerAddressCrossShardThroughSCFlag,
MigrateCodeLeafFlag,
}
91 changes: 91 additions & 0 deletions builtInFunctions/migrateCodeLeaf.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package builtInFunctions

import (
"fmt"
"sync"

"github.com/multiversx/mx-chain-core-go/core/check"
vmcommon "github.com/multiversx/mx-chain-vm-common-go"
)

const noOfArgsMigrateCodeLeaf = 0

type migrateCodeLeaf struct {
baseActiveHandler
accounts vmcommon.AccountsAdapter
gasCost uint64
mutExecution sync.RWMutex
}

// NewMigrateCodeLeafFunc creates a new removeCodeLeaf built-in function component
func NewMigrateCodeLeafFunc(
gasCost uint64,
enableEpochsHandler vmcommon.EnableEpochsHandler,
accounts vmcommon.AccountsAdapter,
) (*migrateCodeLeaf, error) {
if check.IfNil(enableEpochsHandler) {
return nil, ErrNilEnableEpochsHandler
}
if check.IfNil(accounts) {
return nil, ErrNilAccountsAdapter
}

mdt := &migrateCodeLeaf{
gasCost: gasCost,
accounts: accounts,
}

mdt.baseActiveHandler.activeHandler = func() bool {
return enableEpochsHandler.IsFlagEnabled(MigrateCodeLeafFlag)
}
return mdt, nil
}

// ProcessBuiltinFunction will remove trie code leaf corresponding to specified codeHash
func (rcl *migrateCodeLeaf) ProcessBuiltinFunction(
_, acntDst vmcommon.UserAccountHandler,
vmInput *vmcommon.ContractCallInput,
) (*vmcommon.VMOutput, error) {
if vmInput == nil {
return nil, ErrNilVmInput
}
if len(vmInput.Arguments) != noOfArgsMigrateCodeLeaf {
return nil, fmt.Errorf("%w, expected %d, got %d ", ErrInvalidNumberOfArguments, noOfArgsMigrateCodeLeaf, len(vmInput.Arguments))
}
if vmInput.CallValue.Cmp(zero) != 0 {
return nil, ErrBuiltInFunctionCalledWithValue
}
if check.IfNil(acntDst) {
return nil, ErrNilSCDestAccount
}

err := rcl.accounts.MigrateCodeLeaf(acntDst)
if err != nil {
return nil, err
}

rcl.mutExecution.RLock()
gasRemaining := vmInput.GasProvided - rcl.gasCost
rcl.mutExecution.RUnlock()

return &vmcommon.VMOutput{
ReturnCode: vmcommon.Ok,
GasRemaining: gasRemaining,
}, nil
}

// SetNewGasConfig is called whenever gas cost is changed
func (rcl *migrateCodeLeaf) SetNewGasConfig(gasCost *vmcommon.GasCost) {
if gasCost == nil {
return
}

rcl.mutExecution.Lock()
rcl.gasCost = gasCost.BuiltInCost.MigrateCodeLeaf
rcl.mutExecution.Unlock()
}

// IsInterfaceNil returns true if there is no value under the interface
func (rcl *migrateCodeLeaf) IsInterfaceNil() bool {
return rcl == nil
}
186 changes: 186 additions & 0 deletions builtInFunctions/migrateCodeLeaf_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
package builtInFunctions

import (
"errors"
"math/big"
"testing"

"github.com/multiversx/mx-chain-core-go/core"
"github.com/multiversx/mx-chain-core-go/core/check"
vmcommon "github.com/multiversx/mx-chain-vm-common-go"
"github.com/multiversx/mx-chain-vm-common-go/mock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestNewMigrateCodeLeafFunc(t *testing.T) {
t.Parallel()

t.Run("nil enable epochs handler", func(t *testing.T) {
t.Parallel()

rcl, err := NewMigrateCodeLeafFunc(10, nil, &mock.AccountsStub{})
require.Nil(t, rcl)
require.Equal(t, ErrNilEnableEpochsHandler, err)
})

t.Run("nil accounts db", func(t *testing.T) {
t.Parallel()

rcl, err := NewMigrateCodeLeafFunc(10, &mock.EnableEpochsHandlerStub{}, nil)
require.Nil(t, rcl)
require.Equal(t, ErrNilAccountsAdapter, err)
})

t.Run("should work", func(t *testing.T) {
t.Parallel()

enableEpochs := &mock.EnableEpochsHandlerStub{
IsFlagEnabledCalled: func(flag core.EnableEpochFlag) bool {
return flag == MigrateCodeLeafFlag
},
}

rcl, err := NewMigrateCodeLeafFunc(10, enableEpochs, &mock.AccountsStub{})
require.Nil(t, err)
require.False(t, check.IfNil(rcl))

require.True(t, rcl.IsActive())

enableEpochs.IsFlagEnabledCalled = func(flag core.EnableEpochFlag) bool {
return false
}

require.False(t, rcl.IsActive())
})
}

func TestMigrateCodeLeaf_ProcessBuiltinFunction(t *testing.T) {
t.Parallel()

gasCost := uint64(10)

t.Run("nil vm input", func(t *testing.T) {
t.Parallel()

rcl, _ := NewMigrateCodeLeafFunc(gasCost, &mock.EnableEpochsHandlerStub{}, &mock.AccountsStub{})
vmOutput, err := rcl.ProcessBuiltinFunction(mock.NewUserAccount([]byte("sender")), mock.NewUserAccount([]byte("dest")), nil)
assert.Nil(t, vmOutput)
assert.Equal(t, ErrNilVmInput, err)
})

t.Run("invalid num of args", func(t *testing.T) {
t.Parallel()

rcl, _ := NewMigrateCodeLeafFunc(gasCost, &mock.EnableEpochsHandlerStub{}, &mock.AccountsStub{})

addr := []byte("addr")
key := []byte("codeHash")

vmInput := &vmcommon.ContractCallInput{
VMInput: vmcommon.VMInput{
CallerAddr: addr,
GasProvided: 50,
Arguments: [][]byte{key},
CallValue: big.NewInt(0),
},
RecipientAddr: addr,
}

vmOutput, err := rcl.ProcessBuiltinFunction(mock.NewUserAccount([]byte("sender")), mock.NewUserAccount([]byte("dest")), vmInput)
require.Nil(t, vmOutput)
require.True(t, errors.Is(err, ErrInvalidNumberOfArguments))
})

t.Run("should not call with value", func(t *testing.T) {
t.Parallel()

rcl, _ := NewMigrateCodeLeafFunc(gasCost, &mock.EnableEpochsHandlerStub{}, &mock.AccountsStub{})

addr := []byte("addr")

vmInput := &vmcommon.ContractCallInput{
VMInput: vmcommon.VMInput{
CallerAddr: addr,
GasProvided: 50,
CallValue: big.NewInt(2),
},
RecipientAddr: addr,
}

vmOutput, err := rcl.ProcessBuiltinFunction(mock.NewUserAccount([]byte("sender")), mock.NewUserAccount([]byte("dest")), vmInput)
require.Nil(t, vmOutput)
require.Equal(t, ErrBuiltInFunctionCalledWithValue, err)
})

t.Run("nil dest account, should return error", func(t *testing.T) {
t.Parallel()

rcl, _ := NewMigrateCodeLeafFunc(gasCost, &mock.EnableEpochsHandlerStub{}, &mock.AccountsStub{})

addr := []byte("addr")

vmInput := &vmcommon.ContractCallInput{
VMInput: vmcommon.VMInput{
CallerAddr: addr,
GasProvided: 50,
CallValue: big.NewInt(0),
},
RecipientAddr: addr,
}

vmOutput, err := rcl.ProcessBuiltinFunction(mock.NewUserAccount([]byte("sender")), nil, vmInput)
require.Nil(t, vmOutput)
require.Equal(t, ErrNilSCDestAccount, err)
})

t.Run("should work", func(t *testing.T) {
t.Parallel()

wasCalled := false
accounts := &mock.AccountsStub{
GetCodeCalled: func(b []byte) []byte {
return []byte("key code")
},
MigrateCodeLeafCalled: func(account vmcommon.AccountHandler) error {
wasCalled = true
return nil
},
}

rcl, _ := NewMigrateCodeLeafFunc(gasCost, &mock.EnableEpochsHandlerStub{}, accounts)

addr := []byte("addr")

gasProvided := uint64(50)
vmInput := &vmcommon.ContractCallInput{
VMInput: vmcommon.VMInput{
CallerAddr: addr,
GasProvided: gasProvided,
CallValue: big.NewInt(0),
},
RecipientAddr: addr,
}

vmOutput, err := rcl.ProcessBuiltinFunction(mock.NewUserAccount([]byte("sender")), mock.NewUserAccount([]byte("dest")), vmInput)
require.Nil(t, err)
require.NotNil(t, vmOutput)
require.Equal(t, gasProvided-gasCost, vmOutput.GasRemaining)

require.True(t, wasCalled)
})

}

func TestMigrateCodeLeaf_SetNewGasConfig(t *testing.T) {
t.Parallel()

rcl, err := NewMigrateCodeLeafFunc(10, &mock.EnableEpochsHandlerStub{}, &mock.AccountsStub{})
require.Nil(t, err)

require.Equal(t, uint64(10), rcl.gasCost)

rcl.SetNewGasConfig(&vmcommon.GasCost{BuiltInCost: vmcommon.BuiltInCost{MigrateCodeLeaf: 20}})

require.Equal(t, uint64(20), rcl.gasCost)
}
1 change: 1 addition & 0 deletions gasCost.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ type BuiltInCost struct {
GuardAccount uint64
TrieLoadPerNode uint64
TrieStorePerNode uint64
MigrateCodeLeaf uint64
}

// GasCost holds all the needed gas costs for system smart contracts
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.20

require (
github.com/mitchellh/mapstructure v1.4.1
github.com/multiversx/mx-chain-core-go v1.2.19-0.20231129100534-356aa234f4ff
github.com/multiversx/mx-chain-core-go v1.2.19-0.20231207131555-bec3579d08d3
github.com/multiversx/mx-chain-logger-go v1.0.14-0.20231129101244-c44fa1c79b03
github.com/stretchr/testify v1.7.0
)
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxd
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/multiversx/mx-chain-core-go v1.2.19-0.20231129100534-356aa234f4ff h1:Iss44e+2C4vGtQ5sU3lOqDQ+dxvwlO+Z3mSbC8T1J64=
github.com/multiversx/mx-chain-core-go v1.2.19-0.20231129100534-356aa234f4ff/go.mod h1:B5zU4MFyJezmEzCsAHE9YNULmGCm2zbPHvl9hazNxmE=
github.com/multiversx/mx-chain-core-go v1.2.19-0.20231207131555-bec3579d08d3 h1:aRKyuOCTYyCAdMQHXd33Ob5Ze5gKmh5UqaMf0GbkpBs=
github.com/multiversx/mx-chain-core-go v1.2.19-0.20231207131555-bec3579d08d3/go.mod h1:B5zU4MFyJezmEzCsAHE9YNULmGCm2zbPHvl9hazNxmE=
github.com/multiversx/mx-chain-logger-go v1.0.14-0.20231129101244-c44fa1c79b03 h1:krjJTyN9jrFTK0goMGFdgvJGy6bYSqe8EtI/HCceUmU=
github.com/multiversx/mx-chain-logger-go v1.0.14-0.20231129101244-c44fa1c79b03/go.mod h1:fH/fR/GEBsDjPkBoZDVJMoYo2HhlA7++DP6QfITJ1N8=
github.com/multiversx/protobuf v1.3.2 h1:RaNkxvGTGbA0lMcnHAN24qE1G1i+Xs5yHA6MDvQ4mSM=
Expand Down
1 change: 1 addition & 0 deletions interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ type AccountsAdapter interface {
JournalLen() int
RevertToSnapshot(snapshot int) error
GetCode(codeHash []byte) []byte
MigrateCodeLeaf(account AccountHandler) error

RootHash() ([]byte, error)
IsInterfaceNil() bool
Expand Down
10 changes: 10 additions & 0 deletions mock/accountsStub.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type AccountsStub struct {
SetStateCheckpointCalled func(rootHash []byte)
IsPruningEnabledCalled func() bool
GetCodeCalled func([]byte) []byte
MigrateCodeLeafCalled func(account vmcommon.AccountHandler) error
}

// GetCode -
Expand Down Expand Up @@ -127,6 +128,15 @@ func (as *AccountsStub) IsPruningEnabled() bool {
return false
}

// MigrateCodeLeaf -
func (as *AccountsStub) MigrateCodeLeaf(account vmcommon.AccountHandler) error {
if as.MigrateCodeLeafCalled != nil {
return as.MigrateCodeLeafCalled(account)
}

return nil
}

// IsInterfaceNil returns true if there is no value under the interface
func (as *AccountsStub) IsInterfaceNil() bool {
return as == nil
Expand Down