go-ethereum/core/token_validator.go
2024-12-28 09:02:48 +08:00

225 lines
8.7 KiB
Go

// Copyright (c) 2018 XDPoSChain
//
// This program 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.
//
// This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
package core
import (
"fmt"
"math/big"
"math/rand"
"strings"
ethereum "github.com/XinFinOrg/XDPoSChain"
"github.com/XinFinOrg/XDPoSChain/accounts/abi"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus"
"github.com/XinFinOrg/XDPoSChain/contracts/XDCx/contract"
"github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/core/vm"
"github.com/XinFinOrg/XDPoSChain/log"
)
const (
balanceOfFunction = "balanceOf"
minFeeFunction = "minFee"
getDecimalFunction = "decimals"
)
// callMsg implements core.Message to allow passing it as a transaction simulator.
type callMsg struct {
ethereum.CallMsg
}
func (m callMsg) From() common.Address { return m.CallMsg.From }
func (m callMsg) Nonce() uint64 { return 0 }
func (m callMsg) IsFake() bool { return true }
func (m callMsg) To() *common.Address { return m.CallMsg.To }
func (m callMsg) GasPrice() *big.Int { return m.CallMsg.GasPrice }
func (m callMsg) GasFeeCap() *big.Int { return m.CallMsg.GasFeeCap }
func (m callMsg) GasTipCap() *big.Int { return m.CallMsg.GasTipCap }
func (m callMsg) Gas() uint64 { return m.CallMsg.Gas }
func (m callMsg) Value() *big.Int { return m.CallMsg.Value }
func (m callMsg) Data() []byte { return m.CallMsg.Data }
func (m callMsg) BalanceTokenFee() *big.Int { return m.CallMsg.BalanceTokenFee }
func (m callMsg) AccessList() types.AccessList { return m.CallMsg.AccessList }
type SimulatedBackend interface {
CallContractWithState(call ethereum.CallMsg, chain consensus.ChainContext, statedb *state.StateDB) ([]byte, error)
}
// GetTokenAbi return token abi
func GetTokenAbi(tokenAbi string) (*abi.ABI, error) {
contractABI, err := abi.JSON(strings.NewReader(tokenAbi))
if err != nil {
return nil, err
}
return &contractABI, nil
}
// RunContract run smart contract
func RunContract(chain consensus.ChainContext, statedb *state.StateDB, contractAddr common.Address, abi *abi.ABI, method string, args ...interface{}) (interface{}, error) {
input, err := abi.Pack(method, args...)
if err != nil {
return nil, err
}
fakeCaller := common.HexToAddress("0x0000000000000000000000000000000000000001")
statedb.SetBalance(fakeCaller, common.BasePrice)
msg := ethereum.CallMsg{To: &contractAddr, Data: input, From: fakeCaller}
result, err := CallContractWithState(msg, chain, statedb)
if err != nil {
return nil, err
}
var unpackResult interface{}
err = abi.Unpack(&unpackResult, method, result)
if err != nil {
return nil, err
}
return unpackResult, nil
}
// FIXME: please use copyState for this function
// CallContractWithState executes a contract call at the given state.
func CallContractWithState(call ethereum.CallMsg, chain consensus.ChainContext, statedb *state.StateDB) ([]byte, error) {
// Ensure message is initialized properly.
call.GasPrice = big.NewInt(0)
if call.Gas == 0 {
call.Gas = 1000000
}
if call.Value == nil {
call.Value = new(big.Int)
}
// Execute the call.
msg := callMsg{call}
feeCapacity := state.GetTRC21FeeCapacityFromState(statedb)
if msg.To() != nil {
if value, ok := feeCapacity[*msg.To()]; ok {
msg.CallMsg.BalanceTokenFee = value
}
}
txContext := NewEVMTxContext(msg)
evmContext := NewEVMBlockContext(chain.CurrentHeader(), chain, nil)
// Create a new environment which holds all relevant information
// about the transaction and calling mechanisms.
vmenv := vm.NewEVM(evmContext, txContext, statedb, nil, chain.Config(), vm.Config{})
gaspool := new(GasPool).AddGas(1000000)
owner := common.Address{}
result, err := NewStateTransition(vmenv, msg, gaspool).TransitionDb(owner)
if err != nil {
return nil, err
}
return result.Return(), err
}
// make sure that balance of token is at slot 0
func ValidateXDCXApplyTransaction(chain consensus.ChainContext, blockNumber *big.Int, copyState *state.StateDB, tokenAddr common.Address) error {
if blockNumber == nil || blockNumber.Sign() <= 0 {
blockNumber = chain.CurrentHeader().Number
}
if !chain.Config().IsTIPXDCXReceiver(blockNumber) {
return nil
}
contractABI, err := GetTokenAbi(contract.TRC21ABI)
if err != nil {
return fmt.Errorf("ValidateXDCXApplyTransaction: cannot parse ABI. Err: %v", err)
}
if err := ValidateBalanceSlot(chain, copyState, tokenAddr, contractABI); err != nil {
return err
}
if err := ValidateTokenDecimal(chain, copyState, tokenAddr, contractABI); err != nil {
return err
}
return nil
}
// make sure that balance of token is at slot 0
// make sure that minFee of token is at slot 1
func ValidateXDCZApplyTransaction(chain consensus.ChainContext, blockNumber *big.Int, copyState *state.StateDB, tokenAddr common.Address) error {
if blockNumber == nil || blockNumber.Sign() <= 0 {
blockNumber = chain.CurrentHeader().Number
}
if !chain.Config().IsTIPXDCXReceiver(blockNumber) {
return nil
}
contractABI, err := GetTokenAbi(contract.TRC21ABI)
if err != nil {
return fmt.Errorf("ValidateXDCZApplyTransaction: cannot parse ABI. Err: %v", err)
}
// verify balance slot
if err := ValidateBalanceSlot(chain, copyState, tokenAddr, contractABI); err != nil {
return err
}
// validate minFee slot
if err := ValidateMinFeeSlot(chain, copyState, tokenAddr, contractABI); err != nil {
return err
}
return nil
}
func SetRandomBalance(copyState *state.StateDB, tokenAddr, addr common.Address, randomValue *big.Int) {
slotBalanceTrc21 := state.SlotTRC21Token["balances"]
balanceKey := state.GetLocMappingAtKey(addr.Hash(), slotBalanceTrc21)
copyState.SetState(tokenAddr, common.BigToHash(balanceKey), common.BytesToHash(randomValue.Bytes()))
}
func ValidateBalanceSlot(chain consensus.ChainContext, copyState *state.StateDB, tokenAddr common.Address, contractABI *abi.ABI) error {
randBalance := new(big.Int).SetInt64(int64(rand.Intn(1000000000)))
addr := common.HexToAddress("0x0000000000000000000000000000000000000123")
SetRandomBalance(copyState, tokenAddr, addr, randBalance)
result, err := RunContract(chain, copyState, tokenAddr, contractABI, balanceOfFunction, addr)
if err != nil || result == nil {
return fmt.Errorf("cannot get balance at slot %v . Token: %s . Err: %v", state.SlotTRC21Token["balances"], tokenAddr.Hex(), err)
}
balance, ok := result.(*big.Int)
if !ok {
return fmt.Errorf("invalid balance at slot %v . Token: %s . GotBalance: %v . ResultType: %T", state.SlotTRC21Token["balances"], tokenAddr.Hex(), result, result)
}
if balance.Cmp(randBalance) != 0 {
log.Debug("invalid balance slot", "balance_set_at_slot_0", randBalance, "balance_get_from_abi", balance)
return fmt.Errorf("invalid balance slot. Token: %s", tokenAddr.Hex())
}
return nil
}
func ValidateMinFeeSlot(chain consensus.ChainContext, copyState *state.StateDB, tokenAddr common.Address, contractABI *abi.ABI) error {
randomValue := new(big.Int).SetInt64(int64(rand.Intn(1000000000)))
slotMinFeeTrc21 := state.SlotTRC21Token["minFee"]
copyState.SetState(tokenAddr, common.BigToHash(new(big.Int).SetUint64(slotMinFeeTrc21)), common.BytesToHash(randomValue.Bytes()))
result, err := RunContract(chain, copyState, tokenAddr, contractABI, minFeeFunction)
if err != nil || result == nil {
return fmt.Errorf("cannot get minFee at slot %v . Token: %s. Err: %v", state.SlotTRC21Token["minFee"], tokenAddr.Hex(), err)
}
minFee, ok := result.(*big.Int)
if !ok {
return fmt.Errorf("invalid minFee at slot %v . Token: %s . GotMinFee: %v . ResultType: %T", state.SlotTRC21Token["minFee"], tokenAddr.Hex(), result, result)
}
if minFee.Cmp(randomValue) != 0 {
log.Debug("invalid minFee slot", "minFee_set_at_slot_1", randomValue, "minFee_get_from_abi", minFee)
return fmt.Errorf("invalid minFee slot. Token: %s", tokenAddr.Hex())
}
return nil
}
func ValidateTokenDecimal(chain consensus.ChainContext, copyState *state.StateDB, tokenAddr common.Address, contractABI *abi.ABI) error {
result, err := RunContract(chain, copyState, tokenAddr, contractABI, getDecimalFunction)
if err != nil || result == nil {
return fmt.Errorf("cannot get token decimal. Token: %s . Err: %v", tokenAddr.Hex(), err)
}
return nil
}