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/state_transition_test.go

214 lines
7.2 KiB

package core
import (
"crypto/ecdsa"
"fmt"
"math"
"math/big"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/woop-chain/woop/core/types"
"github.com/woop-chain/woop/core/vm"
shardingconfig "github.com/woop-chain/woop/internal/configs/sharding"
"github.com/woop-chain/woop/internal/params"
"github.com/woop-chain/woop/shard"
staking "github.com/woop-chain/woop/staking/types"
"github.com/pkg/errors"
)
type applyStakingMessageTest struct {
name string
tx *staking.StakingTransaction
expectedError error
}
var ApplyStakingMessageTests []applyStakingMessageTest
var key *ecdsa.PrivateKey
func init() {
key, _ = crypto.GenerateKey()
stakingValidatorMissing := errors.New("staking validator does not exist")
ApplyStakingMessageTests = []applyStakingMessageTest{
{
tx: signedCreateValidatorStakingTxn(key),
name: "ApplyStakingMessage_CreateValidator",
},
{
tx: signedEditValidatorStakingTxn(key),
expectedError: stakingValidatorMissing,
name: "ApplyStakingMessage_EditValidator",
},
{
tx: signedDelegateStakingTxn(key),
expectedError: stakingValidatorMissing,
name: "ApplyStakingMessage_Delegate",
},
{
tx: signedUndelegateStakingTxn(key),
expectedError: stakingValidatorMissing,
name: "ApplyStakingMessage_Undelegate",
},
{
tx: signedCollectRewardsStakingTxn(key),
expectedError: errors.New("no rewards to collect"),
name: "ApplyStakingMessage_CollectRewards",
},
}
}
func TestApplyStakingMessages(t *testing.T) {
for _, test := range ApplyStakingMessageTests {
testApplyStakingMessage(test, t)
}
}
func testApplyStakingMessage(test applyStakingMessageTest, t *testing.T) {
chain, db, header, _ := getTestEnvironment(*key)
gp := new(GasPool).AddGas(math.MaxUint64)
t.Run(fmt.Sprintf("%s", test.name), func(t *testing.T) {
// add a fake staking transaction
msg, _ := StakingToMessage(test.tx, header.Number())
// make EVM
ctx := NewEVMContext(msg, header, chain, nil /* coinbase */)
vmenv := vm.NewEVM(ctx, db, params.TestChainConfig, vm.Config{})
// run the staking tx
_, err := ApplyStakingMessage(vmenv, msg, gp)
if err != nil {
if test.expectedError == nil {
t.Errorf(fmt.Sprintf("Got error %v but expected none", err))
} else if test.expectedError.Error() != err.Error() {
t.Errorf(fmt.Sprintf("Got error %v, but expected %v", err, test.expectedError))
}
}
})
}
func TestCollectGas(t *testing.T) {
key, _ := crypto.GenerateKey()
chain, db, header, _ := getTestEnvironment(*key)
header.SetEpoch(new(big.Int).Set(params.LocalnetChainConfig.FeeCollectEpoch))
// set the shard schedule so that fee collectors are available
shard.Schedule = shardingconfig.LocalnetSchedule
feeCollectors := shard.Schedule.InstanceForEpoch(header.Epoch()).FeeCollectors()
if len(feeCollectors) == 0 {
t.Fatal("No fee collectors set")
}
tx := types.NewTransaction(
0, // nonce
common.BytesToAddress([]byte("to")),
0, // shardid
big.NewInt(1e18), // amount, 1 ONE
50000, // gasLimit, intentionally higher than the 21000 required
big.NewInt(100e9), // gasPrice
[]byte{}, // payload, intentionally empty
)
from, _ := tx.SenderAddress()
initialBalance := big.NewInt(2e18)
db.AddBalance(from, initialBalance)
msg, _ := tx.AsMessage(types.NewEIP155Signer(common.Big2))
ctx := NewEVMContext(msg, header, chain, nil /* coinbase is nil, no block reward */)
ctx.TxType = types.SameShardTx
vmenv := vm.NewEVM(ctx, db, params.TestChainConfig, vm.Config{})
gasPool := new(GasPool).AddGas(math.MaxUint64)
_, err := ApplyMessage(vmenv, msg, gasPool)
if err != nil {
t.Fatal(err)
}
// check that sender got the exact expected balance
balance := db.GetBalance(from)
fee := new(big.Int).Mul(tx.GasPrice(), new(big.Int).
SetUint64(
21000, // base fee for normal transfers
))
feePlusAmount := new(big.Int).Add(fee, tx.Value())
expectedBalance := new(big.Int).Sub(initialBalance, feePlusAmount)
if balance.Cmp(expectedBalance) != 0 {
t.Errorf("Balance mismatch for sender: got %v, expected %v", balance, expectedBalance)
}
// check that the fee collectors got half of the fees each
expectedFeePerCollector := new(big.Int).Mul(tx.GasPrice(),
new(big.Int).SetUint64(21000/2))
for collector := range feeCollectors {
balance := db.GetBalance(collector)
if balance.Cmp(expectedFeePerCollector) != 0 {
t.Errorf("Balance mismatch for collector %v: got %v, expected %v",
collector, balance, expectedFeePerCollector)
}
}
// lastly, check the receiver's balance
balance = db.GetBalance(*tx.To())
if balance.Cmp(tx.Value()) != 0 {
t.Errorf("Balance mismatch for receiver: got %v, expected %v", balance, tx.Value())
}
}
func TestCollectGasRounding(t *testing.T) {
// We want to test that the fee collectors get the correct amount of fees
// even if the total fee is not a multiple of the fee ratio.
// For example, if the fee ratio is 1:1, and the total fee is 1e9, then
// the fee collectors should get 0.5e9 each.
// If the gas is 1e9 + 1, then the fee collectors should get 0.5e9 each,
// with the extra 1 being dropped. This test checks for that.
// Such a situation can potentially occur, but is not an immediate concern
// since we require transactions to have a minimum gas price of 100 gwei
// which is always even (in wei) and can be divided across two collectors.
// Hypothetically, a gas price of 1 wei * gas used of (21,000 + odd number)
// could result in such a case which is well handled.
key, _ := crypto.GenerateKey()
chain, db, header, _ := getTestEnvironment(*key)
header.SetEpoch(new(big.Int).Set(params.LocalnetChainConfig.FeeCollectEpoch))
// set the shard schedule so that fee collectors are available
shard.Schedule = shardingconfig.LocalnetSchedule
feeCollectors := shard.Schedule.InstanceForEpoch(header.Epoch()).FeeCollectors()
if len(feeCollectors) == 0 {
t.Fatal("No fee collectors set")
}
tx := types.NewTransaction(
0, // nonce
common.BytesToAddress([]byte("to")),
0, // shardid
big.NewInt(1e18), // amount, 1 ONE
5, // gasLimit
big.NewInt(1), // gasPrice
[]byte{}, // payload, intentionally empty
)
from, _ := tx.SenderAddress()
initialBalance := big.NewInt(2e18)
db.AddBalance(from, initialBalance)
msg, _ := tx.AsMessage(types.NewEIP155Signer(common.Big2))
ctx := NewEVMContext(msg, header, chain, nil /* coinbase is nil, no block reward */)
ctx.TxType = types.SameShardTx
vmenv := vm.NewEVM(ctx, db, params.TestChainConfig, vm.Config{})
gasPool := new(GasPool).AddGas(math.MaxUint64)
st := NewStateTransition(vmenv, msg, gasPool)
// buy gas to set initial gas to 5: gasLimit * gasPrice
if err := st.buyGas(); err != nil {
t.Fatal(err)
}
// set left over gas to 0, so gasUsed is 5
st.gas = 0
st.collectGas()
// check that the fee collectors got the fees in the provided ratio
expectedFeePerCollector := big.NewInt(2) // GIF(5 / 2) = 2
for collector := range feeCollectors {
balance := db.GetBalance(collector)
if balance.Cmp(expectedFeePerCollector) != 0 {
t.Errorf("Balance mismatch for collector %v: got %v, expected %v",
collector, balance, expectedFeePerCollector)
}
}
}