// Copyright 2019 The go-ethereum Authors
// (original work)
// Copyright 2024 The Erigon Authors
// (modifications)
// This file is part of Erigon.
//
// Erigon is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Erigon is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with Erigon. If not, see <http://www.gnu.org/licenses/>.

package vm

import (
	"fmt"
	"slices"
	"strconv"

	"github.com/holiman/uint256"

	"github.com/erigontech/erigon/execution/protocol/params"
	"github.com/erigontech/erigon/execution/types/accounts"
)

var activators = map[int]func(*JumpTable){
	8024: enable8024,
	7702: enable7702,
	7516: enable7516,
	6780: enable6780,
	5656: enable5656,
	4844: enable4844,
	3860: enable3860,
	3855: enable3855,
	3529: enable3529,
	3198: enable3198,
	2929: enable2929,
	2200: enable2200,
	1884: enable1884,
	1344: enable1344,
	1153: enable1153,
}

// EnableEIP enables the given EIP on the config.
// This operation writes in-place, and callers need to ensure that the globally
// defined jump tables are not polluted.
func EnableEIP(eipNum int, jt *JumpTable) error {
	enablerFn, ok := activators[eipNum]
	if !ok {
		return fmt.Errorf("undefined eip %d", eipNum)
	}
	enablerFn(jt)
	validateAndFillMaxStack(jt)
	return nil
}

func ValidEip(eipNum int) bool {
	_, ok := activators[eipNum]
	return ok
}
func ActivateableEips() []string {
	var nums []string //nolint:prealloc
	for k := range activators {
		nums = append(nums, strconv.Itoa(k))
	}
	slices.Sort(nums)
	return nums
}

// enable1884 applies EIP-1884 to the given jump table:
// - Increase cost of BALANCE to 700
// - Increase cost of EXTCODEHASH to 700
// - Increase cost of SLOAD to 800
// - Define SELFBALANCE, with cost GasFastStep (5)
func enable1884(jt *JumpTable) {
	// Gas cost changes
	jt[SLOAD].constantGas = params.SloadGasEIP1884
	jt[BALANCE].constantGas = params.BalanceGasEIP1884
	jt[EXTCODEHASH].constantGas = params.ExtcodeHashGasEIP1884

	// New opcode
	jt[SELFBALANCE] = &operation{
		execute:     opSelfBalance,
		constantGas: GasFastStep,
		numPop:      0,
		numPush:     1,
	}
}

func opSelfBalance(pc uint64, evm *EVM, callContext *CallContext) (uint64, []byte, error) {
	if !callContext.Contract.selfBalanceCached {
		balance, err := evm.IntraBlockState().GetBalance(callContext.Contract.addr)
		if err != nil {
			return pc, nil, err
		}
		callContext.Contract.selfBalance = balance
		callContext.Contract.selfBalanceCached = true
	}
	callContext.Stack.push(callContext.Contract.selfBalance)
	return pc, nil, nil
}

// enable1344 applies EIP-1344 (ChainID Opcode)
// - Adds an opcode that returns the current chain’s EIP-155 unique identifier
func enable1344(jt *JumpTable) {
	// New opcode
	jt[CHAINID] = &operation{
		execute:     opChainID,
		constantGas: GasQuickStep,
		numPop:      0,
		numPush:     1,
	}
}

// opChainID implements CHAINID opcode
func opChainID(pc uint64, evm *EVM, callContext *CallContext) (uint64, []byte, error) {
	chainId := evm.ChainRules().ChainID
	callContext.Stack.push(*chainId)
	return pc, nil, nil
}

// enable2200 applies EIP-2200 (Rebalance net-metered SSTORE)
func enable2200(jt *JumpTable) {
	jt[SLOAD].constantGas = params.SloadGasEIP2200
	jt[SSTORE].dynamicGas = gasSStoreEIP2200
}

// enable2929 enables "EIP-2929: Gas cost increases for state access opcodes"
// https://eips.ethereum.org/EIPS/eip-2929
func enable2929(jt *JumpTable) {
	jt[SSTORE].dynamicGas = gasSStoreEIP2929

	jt[SLOAD].constantGas = 0
	jt[SLOAD].dynamicGas = gasSLoadEIP2929

	jt[EXTCODECOPY].constantGas = params.WarmStorageReadCostEIP2929
	jt[EXTCODECOPY].dynamicGas = gasExtCodeCopyEIP2929

	jt[EXTCODESIZE].constantGas = params.WarmStorageReadCostEIP2929
	jt[EXTCODESIZE].dynamicGas = gasEip2929AccountCheck

	jt[EXTCODEHASH].constantGas = params.WarmStorageReadCostEIP2929
	jt[EXTCODEHASH].dynamicGas = gasEip2929AccountCheck

	jt[BALANCE].constantGas = params.WarmStorageReadCostEIP2929
	jt[BALANCE].dynamicGas = gasEip2929AccountCheck

	jt[CALL].constantGas = params.WarmStorageReadCostEIP2929
	jt[CALL].dynamicGas = gasCallEIP2929

	jt[CALLCODE].constantGas = params.WarmStorageReadCostEIP2929
	jt[CALLCODE].dynamicGas = gasCallCodeEIP2929

	jt[STATICCALL].constantGas = params.WarmStorageReadCostEIP2929
	jt[STATICCALL].dynamicGas = gasStaticCallEIP2929

	jt[DELEGATECALL].constantGas = params.WarmStorageReadCostEIP2929
	jt[DELEGATECALL].dynamicGas = gasDelegateCallEIP2929

	// This was previously part of the dynamic cost, but we're using it as a constantGas
	// factor here
	jt[SELFDESTRUCT].constantGas = params.SelfdestructGasEIP150
	jt[SELFDESTRUCT].dynamicGas = gasSelfdestructEIP2929
}

func enable3529(jt *JumpTable) {
	jt[SSTORE].dynamicGas = gasSStoreEIP3529
	jt[SELFDESTRUCT].dynamicGas = gasSelfdestructEIP3529
}

// enable3198 applies EIP-3198 (BASEFEE Opcode)
// - Adds an opcode that returns the current block's base fee.
func enable3198(jt *JumpTable) {
	// New opcode
	jt[BASEFEE] = &operation{
		execute:     opBaseFee,
		constantGas: GasQuickStep,
		numPop:      0,
		numPush:     1,
	}
}

// enable1153 applies EIP-1153 "Transient Storage"
// - Adds TLOAD that reads from transient storage
// - Adds TSTORE that writes to transient storage
func enable1153(jt *JumpTable) {
	jt[TLOAD] = &operation{
		execute:     opTload,
		constantGas: params.WarmStorageReadCostEIP2929,
		numPop:      1,
		numPush:     1,
	}

	jt[TSTORE] = &operation{
		execute:     opTstore,
		constantGas: params.WarmStorageReadCostEIP2929,
		numPop:      2,
		numPush:     0,
	}
}

// opTload implements TLOAD opcode
func opTload(pc uint64, evm *EVM, scope *CallContext) (uint64, []byte, error) {
	loc := scope.Stack.peek()
	key := accounts.InternKey(loc.Bytes32())
	val := evm.IntraBlockState().GetTransientState(scope.Contract.Address(), key)
	*loc = val
	return pc, nil, nil
}

// opTstore implements TSTORE opcode
func opTstore(pc uint64, evm *EVM, scope *CallContext) (uint64, []byte, error) {
	if evm.readOnly {
		return pc, nil, ErrWriteProtection
	}
	loc := scope.Stack.pop()
	val := scope.Stack.pop()
	evm.IntraBlockState().SetTransientState(scope.Contract.Address(), accounts.InternKey(loc.Bytes32()), val)
	return pc, nil, nil
}

// opBaseFee implements BASEFEE opcode
func opBaseFee(pc uint64, evm *EVM, callContext *CallContext) (uint64, []byte, error) {
	baseFee := evm.Context.BaseFee
	callContext.Stack.push(baseFee)
	return pc, nil, nil
}

// enable3855 applies EIP-3855 (PUSH0 opcode)
func enable3855(jt *JumpTable) {
	// New opcode
	jt[PUSH0] = &operation{
		execute:     opPush0,
		constantGas: GasQuickStep,
		numPop:      0,
		numPush:     1,
	}
}

// opPush0 implements the PUSH0 opcode
func opPush0(pc uint64, evm *EVM, scope *CallContext) (uint64, []byte, error) {
	scope.Stack.push(uint256.Int{})
	return pc, nil, nil
}

// EIP-3860: Limit and meter initcode
// https://eips.ethereum.org/EIPS/eip-3860
func enable3860(jt *JumpTable) {
	jt[CREATE].dynamicGas = gasCreateEip3860
	jt[CREATE2].dynamicGas = gasCreate2Eip3860
}

// enable4844 applies mini-danksharding (BLOBHASH opcode)
// - Adds an opcode that returns the versioned blob hash of the txn at a index.
func enable4844(jt *JumpTable) {
	jt[BLOBHASH] = &operation{
		execute:     opBlobHash,
		constantGas: GasFastestStep,
		numPop:      1,
		numPush:     1,
	}
}

// opBlobHash implements the BLOBHASH opcode
func opBlobHash(pc uint64, evm *EVM, scope *CallContext) (uint64, []byte, error) {
	idx := scope.Stack.peek()
	if idx.LtUint64(uint64(len(evm.BlobHashes))) {
		hash := evm.BlobHashes[idx.Uint64()]
		idx.SetBytes(hash[:])
	} else {
		idx.Clear()
	}
	return pc, nil, nil
}

func opCLZ(pc uint64, evm *EVM, scope *CallContext) (uint64, []byte, error) {
	x := scope.Stack.peek()
	// count leading zero bits in x
	x.SetUint64(256 - uint64(x.BitLen()))
	return pc, nil, nil
}

// enable5656 enables EIP-5656 (MCOPY opcode)
// https://eips.ethereum.org/EIPS/eip-5656
func enable5656(jt *JumpTable) {
	jt[MCOPY] = &operation{
		execute:     opMcopy,
		constantGas: GasFastestStep,
		dynamicGas:  gasMcopy,
		numPop:      3,
		numPush:     0,
		memorySize:  memoryMcopy,
	}
}

// opMcopy implements the MCOPY opcode (https://eips.ethereum.org/EIPS/eip-5656)
func opMcopy(pc uint64, evm *EVM, scope *CallContext) (uint64, []byte, error) {
	var (
		dst    = scope.Stack.pop()
		src    = scope.Stack.pop()
		length = scope.Stack.pop()
	)
	// These values are checked for overflow during memory expansion calculation
	// (the memorySize function on the opcode).
	scope.Memory.Copy(dst.Uint64(), src.Uint64(), length.Uint64())
	return pc, nil, nil
}

// enable6780 applies EIP-6780 (deactivate SELFDESTRUCT)
func enable6780(jt *JumpTable) {
	jt[SELFDESTRUCT].execute = opSelfdestruct6780
}

// opBlobBaseFee implements the BLOBBASEFEE opcode
func opBlobBaseFee(pc uint64, evm *EVM, callContext *CallContext) (uint64, []byte, error) {
	blobBaseFee := evm.Context.BlobBaseFee
	callContext.Stack.push(blobBaseFee)
	return pc, nil, nil
}

// enable7516 applies EIP-7516 (BLOBBASEFEE opcode)
// - Adds an opcode that returns the current block's blob base fee.
func enable7516(jt *JumpTable) {
	jt[BLOBBASEFEE] = &operation{
		execute:     opBlobBaseFee,
		constantGas: GasQuickStep,
		numPop:      0,
		numPush:     1,
	}
}

func enable7702(jt *JumpTable) {
	jt[CALL].dynamicGas = gasCallEIP7702
	jt[CALLCODE].dynamicGas = gasCallCodeEIP7702
	jt[STATICCALL].dynamicGas = gasStaticCallEIP7702
	jt[DELEGATECALL].dynamicGas = gasDelegateCallEIP7702
}

func enable7939(jt *JumpTable) {
	jt[CLZ] = &operation{
		execute:     opCLZ,
		constantGas: GasFastStep,
		numPop:      1,
		numPush:     1,
	}
}

// enable8024 applies EIP-8024 (DUPN, SWAPN, EXCHANGE)
func enable8024(jt *JumpTable) {
	jt[DUPN] = &operation{
		execute:     opDupN,
		constantGas: GasFastestStep,
		numPop:      0,
		numPush:     1,
	}
	jt[SWAPN] = &operation{
		execute:     opSwapN,
		constantGas: GasFastestStep,
		numPop:      0,
		numPush:     0,
	}
	jt[EXCHANGE] = &operation{
		execute:     opExchange,
		constantGas: GasFastestStep,
		numPop:      0,
		numPush:     0,
	}
}

// enable7843 applies EIP-7843 (SLOTNUM)
func enable7843(jt *JumpTable) {
	jt[SLOTNUM] = &operation{
		execute:     opSlotNum,
		constantGas: GasQuickStep,
		numPop:      0,
		numPush:     1,
	}
}

// enable8037 applies EIP-8037 (State Creation Gas Cost Increase)
func enable8037(jt *JumpTable) {
	jt[CREATE].constantGas = params.CreateGasEIP8037
	jt[CREATE2].constantGas = params.Create2GasEIP8037
}

// enable8038 applies EIP-8038 (State-access gas cost update)
func enable8038(jt *JumpTable) {
	jt[EXTCODESIZE].constantGas = params.ExtCodeWarmAccessGasEIP8038
	jt[EXTCODECOPY].constantGas = params.ExtCodeWarmAccessGasEIP8038
	jt[CREATE].constantGas = params.CreateAccessEIP8038
	jt[CREATE2].constantGas = params.CreateAccessEIP8038
}
