aergoio/aergo

View on GitHub
contract/contract.go

Summary

Maintainability
A
3 hrs
Test Coverage
F
19%
package contract

import "C"
import (
    "bytes"
    "context"
    "crypto/sha256"
    "fmt"
    "math/big"
    "regexp"
    "strconv"

    "github.com/aergoio/aergo/v2/fee"
    "github.com/aergoio/aergo/v2/state"
    "github.com/aergoio/aergo/v2/state/statedb"
    "github.com/aergoio/aergo/v2/types"
    "github.com/aergoio/aergo/v2/types/dbkey"
)

var (
    PubNet        bool
    TraceBlockNo  uint64
    bpTimeout     <-chan struct{}
    maxSQLDBSize  uint64
    addressRegexp *regexp.Regexp
)

// These constants indicate the situation in which the contract is executed. BlockFactory refers to execution by a block producer to create a block, from which BlockFactory class calls, and ChainService refers to execution to verify and apply blocks created by other producers. Depending on the mode, the operations or policies may be different, and currently timeout policy is different.
// TODO These values are also used to select slots for internal parallel processing. They have multiple roles or meanings, so it make harder to understand the source code.
const (
    BlockFactory = iota
    ChainService
    MaxVmService
)

func init() {
    addressRegexp, _ = regexp.Compile("^[a-zA-Z0-9]+$")
}

// Execute executes a normal transaction which is possibly executing smart contract.
func Execute(execCtx context.Context, bs *state.BlockState, cdb ChainAccessor, tx *types.Tx, sender, receiver *state.AccountState, bi *types.BlockHeaderInfo, executionMode int, isFeeDelegation bool) (rv string, events []*types.Event, usedFee *big.Int, err error) {

    var (
        txBody     = tx.GetBody()
        txType     = txBody.GetType()
        txPayload  = txBody.GetPayload()
        txAmount   = txBody.GetAmountBigInt()
        txGasLimit = txBody.GetGasLimit()
    )

    // compute the base fee
    usedFee = fee.TxBaseFee(bi.ForkVersion, bs.GasPrice, len(txPayload))

    // transfer the amount from the sender to the receiver
    if err = state.SendBalance(sender, receiver, txBody.GetAmountBigInt()); err != nil {
        return
    }

    // check if the tx is valid and if the code should be executed
    var do_execute bool
    if do_execute, err = checkExecution(txType, txAmount, len(txPayload), bi.ForkVersion, receiver.IsDeploy(), receiver.IsContract()); do_execute != true {
        return
    }

    // compute gas limit
    var gasLimit uint64
    if gasLimit, err = fee.GasLimit(bi.ForkVersion, isFeeDelegation, txGasLimit, len(txPayload), bs.GasPrice, usedFee, sender.Balance(), receiver.Balance()); err != nil {
        err = newVmError(types.ErrNotEnoughGas)
        return
    }

    // open the contract state
    contractState, err := statedb.OpenContractState(receiver.ID(), receiver.State(), bs.StateDB)
    if err != nil {
        return
    }

    // check if this is a contract redeploy
    if receiver.IsRedeploy() {
        // check if the redeploy is valid
        if err = checkRedeploy(sender, receiver, contractState); err != nil {
            return
        }
        // remove the contract from the cache
        bs.RemoveCache(receiver.AccountID())
    }
    var ctrFee *big.Int

    // create a new context
    ctx := NewVmContext(execCtx, bs, cdb, sender, receiver, contractState, sender.ID(), tx.GetHash(), bi, "", true, false, receiver.RP(), executionMode, txBody.GetAmountBigInt(), gasLimit, isFeeDelegation)

    // execute the transaction
    if receiver.IsDeploy() {
        rv, events, ctrFee, err = Create(contractState, txBody.Payload, receiver.ID(), ctx)
    } else {
        rv, events, ctrFee, err = Call(contractState, txBody.Payload, receiver.ID(), ctx)
    }

    // close the trace file
    if ctx.traceFile != nil {
        defer ctx.traceFile.Close()
    }

    // check if the execution fee is negative
    if ctrFee != nil && ctrFee.Sign() < 0 {
        return "", events, usedFee, ErrVmStart
    }
    // add the execution fee to the total fee
    usedFee.Add(usedFee, ctrFee)

    // check if the execution failed
    if err != nil {
        if isSystemError(err) {
            return "", events, usedFee, err
        }
        return "", events, usedFee, newVmError(err)
    }

    // check for sufficient balance for fee
    if isFeeDelegation {
        if receiver.Balance().Cmp(usedFee) < 0 {
            return "", events, usedFee, newVmError(types.ErrInsufficientBalance)
        }
    } else {
        if sender.Balance().Cmp(usedFee) < 0 {
            return "", events, usedFee, newVmError(types.ErrInsufficientBalance)
        }
    }

    // save the contract state
    err = statedb.StageContractState(contractState, bs.StateDB)
    if err != nil {
        return "", events, usedFee, err
    }

    // return the result
    return rv, events, usedFee, nil
}

// check if the tx is valid and if the code should be executed
func checkExecution(txType types.TxType, amount *big.Int, payloadSize int, version int32, isDeploy, isContract bool) (do_execute bool, err error) {

    // check if the receiver is a not contract
    if !isDeploy && !isContract {
        // before the hardfork version 3, all transactions in which the recipient
        // is not a contract were processed as a simple Aergo transfer, including
        // type CALL and FEEDELEGATION.
        // starting from hardfork version 3, transactions expected to CALL a
        // contract but without a valid recipient will emit an error.
        // FEEDELEGATION txns with invalid recipient are rejected on mempool.
        if version >= 3 && txType == types.TxType_CALL {
            // continue and emit an error for correct gas estimation
            // it will fail because there is no code to execute
        } else {
            // no code to execute, just return
            return false, nil
        }
    }

    return true, nil
}

func CreateContractID(account []byte, nonce uint64) []byte {
    h := sha256.New()
    h.Write(account)
    h.Write([]byte(strconv.FormatUint(nonce, 10)))
    recipientHash := h.Sum(nil)                   // byte array with length 32
    return append([]byte{0x0C}, recipientHash...) // prepend 0x0C to make it same length as account addresses
}

func checkRedeploy(sender, receiver *state.AccountState, contractState *statedb.ContractState) error {
    // check if the contract exists
    if !receiver.IsContract() || receiver.IsNew() {
        receiverAddr := types.EncodeAddress(receiver.ID())
        ctrLgr.Warn().Str("error", "not found contract").Str("contract", receiverAddr).Msg("redeploy")
        return newVmError(fmt.Errorf("not found contract %s", receiverAddr))
    }
    // get the contract creator
    creator, err := contractState.GetData(dbkey.CreatorMeta())
    if err != nil {
        return err
    }
    // check if the sender is the creator
    if !bytes.Equal(creator, []byte(types.EncodeAddress(sender.ID()))) {
        return newVmError(types.ErrCreatorNotMatch)
    }
    // no problem found
    return nil
}

func SetStateSQLMaxDBSize(size uint64) {
    if size > stateSQLMaxDBSize {
        maxSQLDBSize = stateSQLMaxDBSize
    } else if size < stateSQLMinDBSize {
        maxSQLDBSize = stateSQLMinDBSize
    } else {
        maxSQLDBSize = size
    }
    //sqlLgr.Info().Uint64("size", maxSQLDBSize).Msg("set max database size(MB)")
}

func StrHash(d string) []byte {
    // using real address
    if len(d) == types.EncodedAddressLength && addressRegexp.MatchString(d) {
        return types.ToAddress(d)
    } else {
        // using alias
        h := sha256.New()
        h.Write([]byte(d))
        b := h.Sum(nil)
        b = append([]byte{0x0C}, b...)
        return b
    }
}