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.
214 lines
7.2 KiB
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/harmony-one/harmony/core/types"
|
|
"github.com/harmony-one/harmony/core/vm"
|
|
shardingconfig "github.com/harmony-one/harmony/internal/configs/sharding"
|
|
"github.com/harmony-one/harmony/internal/params"
|
|
"github.com/harmony-one/harmony/shard"
|
|
staking "github.com/harmony-one/harmony/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)
|
|
}
|
|
}
|
|
}
|
|
|