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/staking/precompile_test.go

204 lines
9.8 KiB

Resolve harmony-one/bounties#77: Staking precompiles (#3906) * Resolve harmony-one/bounties#77: Staking precompiles Create write capable precompiles that can perform staking transactions Add hard fork logic (EpochTBD) for these precompiles Tests for new code with at least 80% unit test coverage Staking library + tests in MaxMustermann2/harmony-staking-precompiles * Fix small typo in comment * Run goimports on files to fix Travis * Do not activate staking precompile on shard 0 * Cascade readOnly to WriteCapableContract * No overlap in readOnly + writeCapable precompiles * Use function selector instead of directive From Solidity, use abi.encodeWithSelector and match it against the exact ABI of the functions. This allows us to remove the need for a directive (32) being encoded, and thus saves 28 bytes of data. * Do not allow contracts to become validators As discussed with Jacky on #3906 * Merge harmony-one/harmony/main properly this time * Run goimports * Update gas calculation for staking precompile Please see comment in core/vm/contracts_write.go RequiredGas * Do not allow contract to become validator (2/2) * Cache StakeMsgs from precompiled transactions Add the StakeMsgs to ProcessorResult and cascade them in insertChain * Remove ContractCode fields from validators Since smart contracts can no longer beecome validators, this field is superfluous. Remove it from the Wrapper structure, and do not assign it a value when creating a validator. Build and goimports checked * Update comments in response to feedback (1) Comments to start with function names (2) Comments for public variables (3) Comment to match function name RunPrecompiledContract (4) Clarify that CreateValidatorFunc + EditValidatorFunc are still used * Fix Travis build by reverting rosetta change * Add revert capability to 3 staking tx types - Delegate - Undelegate - CollectRewards * Fix build: Update evm_test for ValidatorWrapper * Merge main into harmony-staking-precompiles * Add gas for precompile calls and allow EOA usage - Each time the precompile is called, charge the base gas fee plus data cost (if data can be parsed successfully). A gas fee is added to prevent benevolent contract deployers from subsidizing the staking transactions for EOAs through repeated assembly `delegatecall`. - Allow EOAs to use the staking precompile directly. Some changes to the Solidity library are associated with this change. - Remove bytes from parsing address, since the ABI unpacks it into an address format correctly. - Add or update tests. Test coverage report to be attached to the PR shortly. * Run goimports * Check read only and write capable for overlap * Handle precompile stakeMsgs for block proposer The staking precompile generates staking messages which are cascaded to the block via the EVM in `state_processor.go`. This change cascades them in `worker.go` to allow block proposers and block verifiers to keep the same state. * Run goimports for cf2dfac4081444e36a120c9432f4e.. * Update staking precompile epoch to 2 for localnet Bring it in line with staking epoch. Change effects all configurations except mainnet and testnet. `goimports` included. * Add read only precompile to fetch the epoch num * Move epoch precompile to 250 * precompiles: left pad the returned epoch number * chainConfig: check epochs for precompiles panic if staking precompile epoch < pre staking epoch * Add staking migration precompile - Lives at address 251 - Migrates delegations + pending undelegations from address A to B - Useful if address A is hacked - Charges gas of 21k + cost of bytes for two addresses - Does not remove existing delegations, just sets them to zero. Replicates current undelegate setup - Unit tests and `goimports` included. Integration test following shortly in MaxMustermann2/harmony-staking-precompiles * Migration precompile: merge into staking Merge the two precompiles into one, add gas calculation for migration precompile. Move epoch precompile to 251 as a result. When migrating, add undelegations to `To`'s existing undelegations, if any match the epoch. * Add migration gas test, remove panic, add check In response to review comments, add tests for migration gas wherein there are 0/1/2 delegations to migrate. Add the index out of bound check to migration gas calculator and remove panics. Lastly, re-sort migrated undelegations if no existing undelegation in the same epoch was found on `To`. * Move undelegations sorting to end of loop
3 years ago
package staking
import (
"errors"
"fmt"
"math/big"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/harmony-one/harmony/common/denominations"
stakingTypes "github.com/harmony-one/harmony/staking/types"
)
func TestValidateContractAddress(t *testing.T) {
input := []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 55}
args := map[string]interface{}{}
expectedError := errors.New("Cannot parse address from <nil>")
if _, err := ValidateContractAddress(common.BytesToAddress(input), args, "ValidatorAddress"); err != nil {
if expectedError.Error() != err.Error() {
t.Errorf("Expected error %v, got %v", expectedError, err)
}
} else {
t.Errorf("Expected error %v, got result", expectedError)
}
}
func TestParseBigIntFromKey(t *testing.T) {
args := map[string]interface{}{}
expectedError := errors.New("Cannot parse BigInt from <nil>")
if _, err := ParseBigIntFromKey(args, "PotentialBigInt"); err != nil {
if expectedError.Error() != err.Error() {
t.Errorf("Expected error %v, got %v", expectedError, err)
}
} else {
t.Errorf("Expected error %v, got result", expectedError)
}
}
type parseTest struct {
input []byte
name string
expectedError error
expected interface{}
}
var ParseStakeMsgTests = []parseTest{
{
input: []byte{109, 107, 47, 120},
expectedError: errors.New("no method with id: 0x6d6b2f78"),
name: "badStakingKind",
},
{
input: []byte{0, 0},
expectedError: errors.New("data too short (2 bytes) for abi method lookup"),
name: "malformedInput",
},
{
input: []byte{109, 107, 47, 119, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 55},
expected: &stakingTypes.CollectRewards{DelegatorAddress: common.HexToAddress("0x1337")},
name: "collectRewardsSuccess",
},
{
input: []byte{109, 107, 47, 119, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 56},
expectedError: errors.New("[StakingPrecompile] Address mismatch, expected 0x0000000000000000000000000000000000001337 have 0x0000000000000000000000000000000000001338"),
name: "collectRewardsAddressMismatch",
},
{
input: []byte{109, 107, 47, 119, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19},
expectedError: errors.New("abi: cannot marshal in to go type: length insufficient 31 require 32"),
name: "collectRewardsInvalidABI",
},
{
input: []byte{81, 11, 17, 187, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 55, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 107, 199, 94, 45, 99, 16, 0, 0},
expected: &stakingTypes.Delegate{
DelegatorAddress: common.HexToAddress("0x1337"),
ValidatorAddress: common.HexToAddress("0x1338"),
Amount: new(big.Int).Mul(big.NewInt(denominations.One), big.NewInt(100)),
},
name: "delegateSuccess",
},
{
input: []byte{81, 11, 17, 187, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 55, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 107, 199, 94, 45, 99, 16, 0},
expectedError: errors.New("abi: cannot marshal in to go type: length insufficient 95 require 96"),
name: "delegateInvalidABI",
},
{
input: []byte{81, 11, 17, 187, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 55, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 107, 199, 94, 45, 99, 16, 0, 0},
expectedError: errors.New("[StakingPrecompile] Address mismatch, expected 0x0000000000000000000000000000000000001337 have 0x0000000000000000000000000000000000001338"),
name: "delegateAddressMismatch",
},
{
input: []byte{189, 168, 192, 233, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 55, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 107, 199, 94, 45, 99, 16, 0, 0},
expected: &stakingTypes.Undelegate{
DelegatorAddress: common.HexToAddress("0x1337"),
ValidatorAddress: common.HexToAddress("0x1338"),
Amount: new(big.Int).Mul(big.NewInt(denominations.One), big.NewInt(100)),
},
name: "undelegateSuccess",
},
{
input: []byte{189, 168, 192, 233, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 55, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 107, 199, 94, 45, 99, 16, 0},
expectedError: errors.New("abi: cannot marshal in to go type: length insufficient 95 require 96"),
name: "undelegateInvalidABI",
},
{
input: []byte{189, 168, 192, 233, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 55, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 107, 199, 94, 45, 99, 16, 0, 0},
expectedError: errors.New("[StakingPrecompile] Address mismatch, expected 0x0000000000000000000000000000000000001337 have 0x0000000000000000000000000000000000001338"),
name: "undelegateAddressMismatch",
},
{
input: []byte{42, 5, 187, 113},
expectedError: errors.New("abi: attempting to unmarshall an empty string while arguments are expected"),
name: "yesMethodNoData",
},
{
input: []byte{0, 0},
expectedError: errors.New("data too short (2 bytes) for abi method lookup"),
name: "malformedInput",
},
{
input: []byte{42, 5, 187, 113, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 55, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 56},
expected: &stakingTypes.MigrationMsg{
From: common.HexToAddress("0x1337"),
To: common.HexToAddress("0x1338"),
},
name: "migrationSuccess",
},
{
input: []byte{42, 5, 187, 113, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 55},
expectedError: errors.New("[StakingPrecompile] Address mismatch, expected 0x0000000000000000000000000000000000001337 have 0x0000000000000000000000000000000000001338"),
name: "migrationAddressMismatch",
},
{
input: []byte{42, 6, 187, 113, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 55},
expectedError: errors.New("no method with id: 0x2a06bb71"),
name: "migrationNoMatchingMethod",
},
{
input: []byte{42, 5, 187, 113, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 55, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19},
expectedError: errors.New("abi: cannot marshal in to go type: length insufficient 63 require 64"),
name: "migrationAddressMismatch",
},
}
func testParseStakeMsg(test parseTest, t *testing.T) {
t.Run(fmt.Sprintf("%s", test.name), func(t *testing.T) {
if res, err := ParseStakeMsg(common.HexToAddress("1337"), test.input); err != nil {
if test.expectedError != nil {
if test.expectedError.Error() != err.Error() {
t.Errorf("Expected error %v, got %v", test.expectedError, err)
}
} else {
t.Error(err)
}
} else {
if test.expectedError != nil {
t.Errorf("Expected an error %v but instead got result %v", test.expectedError, res)
}
if test.expected != nil {
if converted, ok := res.(*stakingTypes.Delegate); ok {
convertedExp, ok := test.expected.(*stakingTypes.Delegate)
if !ok {
t.Errorf("Could not converted test.expected to *stakingTypes.Delegate")
} else if !converted.Equals(*convertedExp) {
t.Errorf("Expected %+v but got %+v", test.expected, converted)
}
} else if converted, ok := res.(*stakingTypes.Undelegate); ok {
convertedExp, ok := test.expected.(*stakingTypes.Undelegate)
if !ok {
t.Errorf("Could not converted test.expected to *stakingTypes.Undelegate")
} else if !converted.Equals(*convertedExp) {
t.Errorf("Expected %+v but got %+v", test.expected, converted)
}
} else if converted, ok := res.(*stakingTypes.CollectRewards); ok {
convertedExp, ok := test.expected.(*stakingTypes.CollectRewards)
if !ok {
t.Errorf("Could not converted test.expected to *stakingTypes.CollectRewards")
} else if !converted.Equals(*convertedExp) {
t.Errorf("Expected %+v but got %+v", test.expected, converted)
}
} else if converted, ok := res.(*stakingTypes.MigrationMsg); ok {
convertedExp, ok := test.expected.(*stakingTypes.MigrationMsg)
if !ok {
t.Errorf("Could not converted test.expected to *stakingTypes.MigrationMsg")
} else if !converted.Equals(*convertedExp) {
t.Errorf("Expected %+v but got %+v", test.expected, converted)
}
} else {
panic("Received unexpected result from ParseStakeMsg")
}
} else if res != nil {
t.Errorf("Expected nil, got %v", res)
}
}
})
}
func TestParseStakeMsgs(t *testing.T) {
for _, test := range ParseStakeMsgTests {
testParseStakeMsg(test, t)
}
}