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/core/vm/contracts_write.go

305 lines
10 KiB

package vm
import (
"errors"
"math/big"
"strings"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/rlp"
"github.com/woop-chain/woop/accounts/abi"
"github.com/woop-chain/woop/core/types"
"github.com/woop-chain/woop/shard"
"github.com/woop-chain/woop/staking"
stakingTypes "github.com/woop-chain/woop/staking/types"
)
// WriteCapablePrecompiledContractsStaking lists out the write capable precompiled contracts
// which are available after the StakingPrecompileEpoch
// for now, we have only one contract at 252 or 0xfc - which is the staking precompile
var WriteCapablePrecompiledContractsStaking = map[common.Address]WriteCapablePrecompiledContract{
common.BytesToAddress([]byte{252}): &stakingPrecompile{},
}
// WriteCapablePrecompiledContractsCrossXfer lists out the write capable precompiled contracts
// which are available after the CrossShardXferPrecompileEpoch
// It includes the staking precompile and the cross-shard transfer precompile
var WriteCapablePrecompiledContractsCrossXfer = map[common.Address]WriteCapablePrecompiledContract{
// reserve 250 for read only staking precompile and 251 for epoch
common.BytesToAddress([]byte{249}): &crossShardXferPrecompile{},
common.BytesToAddress([]byte{252}): &stakingPrecompile{},
}
// WriteCapablePrecompiledContract represents the interface for Native Go contracts
// which are available as a precompile in the EVM
// As with (read-only) PrecompiledContracts, these need a RequiredGas function
// Note that these contracts have the capability to alter the state
// while those in contracts.go do not
type WriteCapablePrecompiledContract interface {
// RequiredGas calculates the contract gas use
RequiredGas(evm *EVM, contract *Contract, input []byte) (uint64, error)
// use a different name from read-only contracts to be safe
RunWriteCapable(evm *EVM, contract *Contract, input []byte) ([]byte, error)
}
// RunWriteCapablePrecompiledContract runs and evaluates the output of a write capable precompiled contract.
func RunWriteCapablePrecompiledContract(
p WriteCapablePrecompiledContract,
evm *EVM,
contract *Contract,
input []byte,
readOnly bool,
) ([]byte, error) {
// immediately error out if readOnly
if readOnly {
return nil, errWriteProtection
}
gas, err := p.RequiredGas(evm, contract, input)
if err != nil {
return nil, err
}
if !contract.UseGas(gas) {
return nil, ErrOutOfGas
}
return p.RunWriteCapable(evm, contract, input)
}
type stakingPrecompile struct{}
// RequiredGas returns the gas required to execute the pre-compiled contract.
//
// This method does not require any overflow checking as the input size gas costs
// required for anything significant is so high it's impossible to pay for.
func (c *stakingPrecompile) RequiredGas(
evm *EVM,
contract *Contract,
input []byte,
) (uint64, error) {
// if invalid data or invalid shard
// set payload to blank and charge minimum gas
var payload []byte = make([]byte, 0)
// availability of staking and precompile has already been checked
if evm.Context.ShardID == shard.BeaconChainShardID {
// check that input is well formed
// meaning all the expected parameters are available
// and that we are only trying to perform staking tx
// on behalf of the correct entity
stakeMsg, err := staking.ParseStakeMsg(contract.Caller(), input)
if err == nil {
// otherwise charge similar to a regular staking tx
if migrationMsg, ok := stakeMsg.(*stakingTypes.MigrationMsg); ok {
// charge per delegation to migrate
return evm.CalculateMigrationGas(evm.StateDB,
migrationMsg,
evm.ChainConfig().IsS3(evm.EpochNumber),
evm.ChainConfig().IsIstanbul(evm.EpochNumber),
)
} else if encoded, err := rlp.EncodeToBytes(stakeMsg); err == nil {
payload = encoded
}
}
}
if gas, err := IntrinsicGas(
payload,
false, // contractCreation
evm.ChainConfig().IsS3(evm.EpochNumber), // homestead
evm.ChainConfig().IsIstanbul(evm.EpochNumber), // istanbul
false, // isValidatorCreation
); err != nil {
return 0, err // ErrOutOfGas occurs when gas payable > uint64
} else {
return gas, nil
}
}
// RunWriteCapable runs the actual contract (that is it performs the staking)
func (c *stakingPrecompile) RunWriteCapable(
evm *EVM,
contract *Contract,
input []byte,
) ([]byte, error) {
if evm.Context.ShardID != shard.BeaconChainShardID {
return nil, errors.New("Staking not supported on this shard")
}
stakeMsg, err := staking.ParseStakeMsg(contract.Caller(), input)
if err != nil {
return nil, err
}
var rosettaBlockTracer RosettaTracer
if tmpTracker, ok := evm.vmConfig.Tracer.(RosettaTracer); ok {
rosettaBlockTracer = tmpTracker
}
if delegate, ok := stakeMsg.(*stakingTypes.Delegate); ok {
if err := evm.Delegate(evm.StateDB, rosettaBlockTracer, delegate); err != nil {
return nil, err
} else {
evm.StakeMsgs = append(evm.StakeMsgs, delegate)
return nil, nil
}
}
if undelegate, ok := stakeMsg.(*stakingTypes.Undelegate); ok {
return nil, evm.Undelegate(evm.StateDB, rosettaBlockTracer, undelegate)
}
if collectRewards, ok := stakeMsg.(*stakingTypes.CollectRewards); ok {
return nil, evm.CollectRewards(evm.StateDB, rosettaBlockTracer, collectRewards)
}
// Migrate is not supported in precompile and will be done in a batch hard fork
//if migrationMsg, ok := stakeMsg.(*stakingTypes.MigrationMsg); ok {
// stakeMsgs, err := evm.MigrateDelegations(evm.StateDB, migrationMsg)
// if err != nil {
// return nil, err
// } else {
// for _, stakeMsg := range stakeMsgs {
// if delegate, ok := stakeMsg.(*stakingTypes.Delegate); ok {
// evm.StakeMsgs = append(evm.StakeMsgs, delegate)
// } else {
// return nil, errors.New("[StakingPrecompile] Received incompatible stakeMsg from evm.MigrateDelegations")
// }
// }
// return nil, nil
// }
//}
return nil, errors.New("[StakingPrecompile] Received incompatible stakeMsg from staking.ParseStakeMsg")
}
var abiCrossShardXfer abi.ABI
func init() {
// msg.Value is used for transfer and is also a parameter
// otherwise it might be possible for a user to retrieve money from the precompile
// that was sent by someone else prior to the hard fork
// contract.Caller is used as fromAddress, not a parameter
// originating ShardID is pulled from the EVM object, not a parameter
crossShardXferABIJSON := `
[
{
"inputs": [
{
"internalType": "uint256",
"name": "value",
"type": "uint256"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint64",
"name": "toShardID",
"type": "uint32"
}
],
"name": "crossShardTransfer",
"outputs": [],
"stateMutability": "payable",
"type": "function"
}
]`
var err error
abiCrossShardXfer, err = abi.JSON(strings.NewReader(crossShardXferABIJSON))
if err != nil {
// means an error in the code
panic("Invalid cross shard transfer ABI JSON")
}
}
type crossShardXferPrecompile struct{}
// RequiredGas returns the gas required to execute the pre-compiled contract.
//
// This method does not require any overflow checking as the input size gas costs
// required for anything significant is so high it's impossible to pay for.
func (c *crossShardXferPrecompile) RequiredGas(
evm *EVM,
contract *Contract,
input []byte,
) (uint64, error) {
// multiple instances of the precompile in one transaction
// are blocked, so there is no way for a smart contract
// to subsidize this transaction by an EOA via delegatecall
// therefore no need to charge any gas
return 0, nil
}
// RunWriteCapable runs the actual contract
func (c *crossShardXferPrecompile) RunWriteCapable(
evm *EVM,
contract *Contract,
input []byte,
) ([]byte, error) {
// make sure that cxReceipt is already nil to
// prevent multiple calls to the precompile
// in the same transaction
if evm.CXReceipt != nil {
return nil, errors.New("cannot call cross shard precompile again in same tx")
}
fromAddress, toAddress, fromShardID, toShardID, value, err :=
parseCrossShardXferData(evm, contract, input)
if err != nil {
return nil, err
}
// validate not a contract (toAddress can still be a contract)
if len(evm.StateDB.GetCode(fromAddress)) > 0 && !evm.IsValidator(evm.StateDB, fromAddress) {
return nil, errors.New("cross shard xfer not yet implemented for contracts")
}
// can't have too many shards
if toShardID >= evm.Context.NumShards {
return nil, errors.New("toShardId out of bounds")
}
// not for simple transfers
if fromShardID == toShardID {
return nil, errors.New("from and to shard id can't be equal")
}
// make sure nobody sends extra or less money
if contract.Value().Cmp(value) != 0 {
return nil, errors.New("argument value and msg.value not equal")
}
// now do the actual transfer
// step 1 -> remove funds from the precompile address
if !evm.CanTransfer(evm.StateDB, contract.Address(), value) {
return nil, errors.New("not enough balance received")
}
evm.Transfer(evm.StateDB, contract.Address(), toAddress, value, types.SubtractionOnly)
// step 2 -> make a cross link
// note that the transaction hash is added by state_processor.go to this receipt
// and that the receiving shard does not care about the `From` but we use the original
// instead of the precompile address for consistency
evm.CXReceipt = &types.CXReceipt{
From: fromAddress,
To: &toAddress,
ShardID: fromShardID,
ToShardID: toShardID,
Amount: value,
}
return nil, nil
}
// parseCrossShardXferData does a simple parse with only data types validation
func parseCrossShardXferData(evm *EVM, contract *Contract, input []byte) (
common.Address, common.Address, uint32, uint32, *big.Int, error) {
method, err := abiCrossShardXfer.MethodById(input)
if err != nil {
return common.Address{}, common.Address{}, 0, 0, nil, err
}
input = input[4:]
args := map[string]interface{}{}
if err = method.Inputs.UnpackIntoMap(args, input); err != nil {
return common.Address{}, common.Address{}, 0, 0, nil, err
}
value, err := abi.ParseBigIntFromKey(args, "value")
if err != nil {
return common.Address{}, common.Address{}, 0, 0, nil, err
}
toAddress, err := abi.ParseAddressFromKey(args, "to")
if err != nil {
return common.Address{}, common.Address{}, 0, 0, nil, err
}
toShardID, err := abi.ParseUint32FromKey(args, "toShardID")
if err != nil {
return common.Address{}, common.Address{}, 0, 0, nil, err
}
return contract.Caller(), toAddress, evm.ShardID, toShardID, value, nil
}