The core protocol of WoopChain
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
woop/rpc/contract.go

199 lines
5.8 KiB

package rpc
import (
"context"
"fmt"
"math"
"math/big"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/rpc"
"github.com/harmony-one/harmony/common/denominations"
"github.com/harmony-one/harmony/core"
"github.com/harmony-one/harmony/core/types"
"github.com/harmony-one/harmony/core/vm"
"github.com/harmony-one/harmony/hmy"
hmyCommon "github.com/harmony-one/harmony/internal/common"
"github.com/harmony-one/harmony/internal/utils"
)
const (
defaultGasPrice = denominations.Nano
defaultFromAddress = "0x0000000000000000000000000000000000000000"
)
// PublicContractService provides an API to access Harmony's contract services.
// It offers only methods that operate on public data that is freely available to anyone.
type PublicContractService struct {
hmy *hmy.Harmony
version Version
}
// NewPublicContractAPI creates a new API for the RPC interface
func NewPublicContractAPI(hmy *hmy.Harmony, version Version) rpc.API {
return rpc.API{
Namespace: version.Namespace(),
Version: APIVersion,
Service: &PublicContractService{hmy, version},
Public: true,
}
}
// Call executes the given transaction on the state for the given block number.
// It doesn't make and changes in the state/blockchain and is useful to execute and retrieve values.
func (s *PublicContractService) Call(
ctx context.Context, args CallArgs, blockNumber BlockNumber,
) (hexutil.Bytes, error) {
// Process number based on version
blockNum := blockNumber.EthBlockNumber()
// Execute call
result, err := doCall(ctx, s.hmy, args, blockNum, vm.Config{}, CallTimeout, s.hmy.RPCGasCap)
if err != nil {
return nil, err
}
// If VM returns error, still return the ReturnData, which is the contract error message
return result.ReturnData, nil
}
// GetCode returns the code stored at the given address in the state for the given block number.
func (s *PublicContractService) GetCode(
ctx context.Context, addr string, blockNumber BlockNumber,
) (hexutil.Bytes, error) {
// Process number based on version
blockNum := blockNumber.EthBlockNumber()
// Fetch state
address := hmyCommon.ParseAddr(addr)
state, _, err := s.hmy.StateAndHeaderByNumber(ctx, blockNum)
if state == nil || err != nil {
return nil, err
}
code := state.GetCode(address)
// Response output is the same for all versions
return code, state.Error()
}
// GetStorageAt returns the storage from the state at the given address, key and
// block number. The rpc.LatestBlockNumber and rpc.PendingBlockNumber meta block
// numbers are also allowed.
func (s *PublicContractService) GetStorageAt(
ctx context.Context, addr string, key string, blockNumber BlockNumber,
) (hexutil.Bytes, error) {
// Process number based on version
blockNum := blockNumber.EthBlockNumber()
// Fetch state
state, _, err := s.hmy.StateAndHeaderByNumber(ctx, blockNum)
if state == nil || err != nil {
return nil, err
}
address := hmyCommon.ParseAddr(addr)
res := state.GetState(address, common.HexToHash(key))
// Response output is the same for all versions
return res[:], state.Error()
}
// docall executes an EVM call
func doCall(
ctx context.Context, hmy *hmy.Harmony, args CallArgs, blockNum rpc.BlockNumber,
vmCfg vm.Config, timeout time.Duration, globalGasCap *big.Int,
) (core.ExecutionResult, error) {
defer func(start time.Time) {
utils.Logger().Debug().
Dur("runtime", time.Since(start)).
Msg("Executing EVM call finished")
}(time.Now())
// Fetch state
state, header, err := hmy.StateAndHeaderByNumber(ctx, blockNum)
if state == nil || err != nil {
return core.ExecutionResult{}, err
}
// Set sender address or use a default if none specified
var addr common.Address
if args.From == nil {
// Any address does not affect the logic of this call.
addr = common.HexToAddress(defaultFromAddress)
} else {
addr = *args.From
}
// Set default gas & gas price if none were set
gas := uint64(math.MaxUint64 / 2)
if args.Gas != nil {
gas = uint64(*args.Gas)
}
if globalGasCap != nil && globalGasCap.Uint64() < gas {
utils.Logger().Warn().
Uint64("requested", gas).
Uint64("cap", globalGasCap.Uint64()).
Msg("Caller gas above allowance, capping")
gas = globalGasCap.Uint64()
}
gasPrice := new(big.Int).SetUint64(defaultGasPrice)
if args.GasPrice != nil {
gasPrice = args.GasPrice.ToInt()
}
// Set value & data
value := new(big.Int)
if args.Value != nil {
value = args.Value.ToInt()
}
var data []byte
if args.Data != nil {
data = *args.Data
}
// Create new call message
msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, data, false)
// Setup context so it may be cancelled the call has completed
// or, in case of unmetered gas, setup a context with a timeout.
var cancel context.CancelFunc
if timeout > 0 {
ctx, cancel = context.WithTimeout(ctx, timeout)
} else {
ctx, cancel = context.WithCancel(ctx)
}
// Make sure the context is cancelled when the call has completed
// this makes sure resources are cleaned up.
defer cancel()
// Get a new instance of the EVM.
evm, err := hmy.GetEVM(ctx, msg, state, header)
if err != nil {
return core.ExecutionResult{}, err
}
// Wait for the context to be done and cancel the evm. Even if the
// EVM has finished, cancelling may be done (repeatedly)
go func() {
<-ctx.Done()
evm.Cancel()
}()
// Setup the gas pool (also for unmetered requests)
// and apply the message.
gp := new(core.GasPool).AddGas(math.MaxUint64)
result, err := core.ApplyMessage(evm, msg, gp)
if err != nil {
return core.ExecutionResult{}, err
}
// If the timer caused an abort, return an appropriate error message
if evm.Cancelled() {
return core.ExecutionResult{}, fmt.Errorf("execution aborted (timeout = %v)", timeout)
}
// Response output is the same for all versions
return result, nil
}