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 looppull/4024/head
parent
55f8c769a0
commit
d500c4ede6
@ -0,0 +1,85 @@ |
||||
package core |
||||
|
||||
import ( |
||||
"crypto/ecdsa" |
||||
"math/big" |
||||
"testing" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/crypto" |
||||
"github.com/harmony-one/harmony/core/types" |
||||
staking "github.com/harmony-one/harmony/staking/types" |
||||
) |
||||
|
||||
func TestPrepareStakingMetadata(t *testing.T) { |
||||
key, _ := crypto.GenerateKey() |
||||
chain, db, header, _ := getTestEnvironment(*key) |
||||
// fake transaction
|
||||
tx := types.NewTransaction(1, common.BytesToAddress([]byte{0x11}), 0, big.NewInt(111), 1111, big.NewInt(11111), []byte{0x11, 0x11, 0x11}) |
||||
txs := []*types.Transaction{tx} |
||||
|
||||
// fake staking transactions
|
||||
stx1 := signedCreateValidatorStakingTxn(key) |
||||
stx2 := signedDelegateStakingTxn(key) |
||||
stxs := []*staking.StakingTransaction{stx1, stx2} |
||||
|
||||
// make a fake block header
|
||||
block := types.NewBlock(header, txs, []*types.Receipt{types.NewReceipt([]byte{}, false, 0), types.NewReceipt([]byte{}, false, 0), |
||||
types.NewReceipt([]byte{}, false, 0)}, nil, nil, stxs) |
||||
// run it
|
||||
if _, _, err := chain.prepareStakingMetaData(block, []staking.StakeMsg{&staking.Delegate{}}, db); err != nil { |
||||
if err.Error() != "address not present in state" { // when called in test for core/vm
|
||||
t.Errorf("Got error %v in prepareStakingMetaData", err) |
||||
} |
||||
} else { |
||||
// when called independently there is no error
|
||||
} |
||||
} |
||||
|
||||
func signedCreateValidatorStakingTxn(key *ecdsa.PrivateKey) *staking.StakingTransaction { |
||||
stakePayloadMaker := func() (staking.Directive, interface{}) { |
||||
return staking.DirectiveCreateValidator, sampleCreateValidator(*key) |
||||
} |
||||
stx, _ := staking.NewStakingTransaction(0, 1e10, big.NewInt(10000), stakePayloadMaker) |
||||
signed, _ := staking.Sign(stx, staking.NewEIP155Signer(stx.ChainID()), key) |
||||
return signed |
||||
} |
||||
|
||||
func signedEditValidatorStakingTxn(key *ecdsa.PrivateKey) *staking.StakingTransaction { |
||||
stakePayloadMaker := func() (staking.Directive, interface{}) { |
||||
return staking.DirectiveEditValidator, sampleEditValidator(*key) |
||||
} |
||||
stx, _ := staking.NewStakingTransaction(0, 1e10, big.NewInt(10000), stakePayloadMaker) |
||||
signed, _ := staking.Sign(stx, staking.NewEIP155Signer(stx.ChainID()), key) |
||||
return signed |
||||
} |
||||
|
||||
func signedDelegateStakingTxn(key *ecdsa.PrivateKey) *staking.StakingTransaction { |
||||
stakePayloadMaker := func() (staking.Directive, interface{}) { |
||||
return staking.DirectiveDelegate, sampleDelegate(*key) |
||||
} |
||||
// nonce, gasLimit uint64, gasPrice *big.Int, f StakeMsgFulfiller
|
||||
stx, _ := staking.NewStakingTransaction(0, 1e10, big.NewInt(10000), stakePayloadMaker) |
||||
signed, _ := staking.Sign(stx, staking.NewEIP155Signer(stx.ChainID()), key) |
||||
return signed |
||||
} |
||||
|
||||
func signedUndelegateStakingTxn(key *ecdsa.PrivateKey) *staking.StakingTransaction { |
||||
stakePayloadMaker := func() (staking.Directive, interface{}) { |
||||
return staking.DirectiveUndelegate, sampleUndelegate(*key) |
||||
} |
||||
// nonce, gasLimit uint64, gasPrice *big.Int, f StakeMsgFulfiller
|
||||
stx, _ := staking.NewStakingTransaction(0, 1e10, big.NewInt(10000), stakePayloadMaker) |
||||
signed, _ := staking.Sign(stx, staking.NewEIP155Signer(stx.ChainID()), key) |
||||
return signed |
||||
} |
||||
|
||||
func signedCollectRewardsStakingTxn(key *ecdsa.PrivateKey) *staking.StakingTransaction { |
||||
stakePayloadMaker := func() (staking.Directive, interface{}) { |
||||
return staking.DirectiveCollectRewards, sampleCollectRewards(*key) |
||||
} |
||||
// nonce, gasLimit uint64, gasPrice *big.Int, f StakeMsgFulfiller
|
||||
stx, _ := staking.NewStakingTransaction(0, 1e10, big.NewInt(10000), stakePayloadMaker) |
||||
signed, _ := staking.Sign(stx, staking.NewEIP155Signer(stx.ChainID()), key) |
||||
return signed |
||||
} |
@ -0,0 +1,478 @@ |
||||
package core |
||||
|
||||
import ( |
||||
"crypto/ecdsa" |
||||
"errors" |
||||
"fmt" |
||||
"math" |
||||
"math/big" |
||||
"testing" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/core/rawdb" |
||||
"github.com/ethereum/go-ethereum/crypto" |
||||
"github.com/ethereum/go-ethereum/ethdb" |
||||
bls_core "github.com/harmony-one/bls/ffi/go/bls" |
||||
"github.com/harmony-one/harmony/block" |
||||
blockfactory "github.com/harmony-one/harmony/block/factory" |
||||
"github.com/harmony-one/harmony/common/denominations" |
||||
"github.com/harmony-one/harmony/core/state" |
||||
"github.com/harmony-one/harmony/core/types" |
||||
"github.com/harmony-one/harmony/core/vm" |
||||
"github.com/harmony-one/harmony/crypto/bls" |
||||
"github.com/harmony-one/harmony/crypto/hash" |
||||
chain2 "github.com/harmony-one/harmony/internal/chain" |
||||
"github.com/harmony-one/harmony/internal/params" |
||||
"github.com/harmony-one/harmony/numeric" |
||||
"github.com/harmony-one/harmony/staking/effective" |
||||
staking "github.com/harmony-one/harmony/staking/types" |
||||
staketest "github.com/harmony-one/harmony/staking/types/test" |
||||
) |
||||
|
||||
func getTestEnvironment(testBankKey ecdsa.PrivateKey) (*BlockChain, *state.DB, *block.Header, ethdb.Database) { |
||||
// initialize
|
||||
var ( |
||||
testBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey) |
||||
testBankFunds = new(big.Int).Mul(big.NewInt(denominations.One), big.NewInt(40000)) |
||||
chainConfig = params.TestChainConfig |
||||
blockFactory = blockfactory.ForTest |
||||
database = rawdb.NewMemoryDatabase() |
||||
gspec = Genesis{ |
||||
Config: chainConfig, |
||||
Factory: blockFactory, |
||||
Alloc: GenesisAlloc{testBankAddress: {Balance: testBankFunds}}, |
||||
ShardID: 0, |
||||
} |
||||
engine = chain2.NewEngine() |
||||
) |
||||
genesis := gspec.MustCommit(database) |
||||
|
||||
// fake blockchain
|
||||
chain, _ := NewBlockChain(database, nil, gspec.Config, engine, vm.Config{}, nil) |
||||
db, _ := chain.StateAt(genesis.Root()) |
||||
|
||||
// make a fake block header (use epoch 1 so that locked tokens can be tested)
|
||||
header := blockFactory.NewHeader(common.Big0) |
||||
|
||||
return chain, db, header, database |
||||
} |
||||
|
||||
func TestEVMStaking(t *testing.T) { |
||||
key, _ := crypto.GenerateKey() |
||||
chain, db, header, database := getTestEnvironment(*key) |
||||
batch := database.NewBatch() |
||||
|
||||
// fake transaction
|
||||
tx := types.NewTransaction(1, common.BytesToAddress([]byte{0x11}), 0, big.NewInt(111), 1111, big.NewInt(11111), []byte{0x11, 0x11, 0x11}) |
||||
// transaction as message (chainId = 2)
|
||||
msg, _ := tx.AsMessage(types.NewEIP155Signer(common.Big2)) |
||||
// context
|
||||
ctx := NewEVMContext(msg, header, chain, nil /* coinbase */) |
||||
|
||||
// createValidator test
|
||||
createValidator := sampleCreateValidator(*key) |
||||
err := ctx.CreateValidator(db, &createValidator) |
||||
if err != nil { |
||||
t.Errorf("Got error %v in CreateValidator", err) |
||||
} |
||||
// write it to snapshot so that we can use it in edit
|
||||
// use a copy because we are editing below (wrapper.Delegations)
|
||||
wrapper, err := db.ValidatorWrapper(createValidator.ValidatorAddress, false, true) |
||||
err = chain.WriteValidatorSnapshot(batch, &staking.ValidatorSnapshot{wrapper, header.Epoch()}) |
||||
// also write the delegation so we can use it in CollectRewards
|
||||
selfIndex := staking.DelegationIndex{ |
||||
createValidator.ValidatorAddress, |
||||
uint64(0), |
||||
common.Big0, // block number at which delegation starts
|
||||
} |
||||
err = chain.writeDelegationsByDelegator(batch, createValidator.ValidatorAddress, []staking.DelegationIndex{selfIndex}) |
||||
|
||||
// editValidator test
|
||||
editValidator := sampleEditValidator(*key) |
||||
editValidator.SlotKeyToRemove = &createValidator.SlotPubKeys[0] |
||||
err = ctx.EditValidator(db, &editValidator) |
||||
if err != nil { |
||||
t.Errorf("Got error %v in EditValidator", err) |
||||
} |
||||
|
||||
// delegate test
|
||||
delegate := sampleDelegate(*key) |
||||
// add undelegations in epoch0
|
||||
wrapper.Delegations[0].Undelegations = []staking.Undelegation{ |
||||
staking.Undelegation{ |
||||
new(big.Int).Mul(big.NewInt(denominations.One), |
||||
big.NewInt(10000)), |
||||
common.Big0, |
||||
}, |
||||
} |
||||
// redelegate using epoch1, so that we can cover the locked tokens use case as well
|
||||
ctx2 := NewEVMContext(msg, blockfactory.ForTest.NewHeader(common.Big1), chain, nil) |
||||
err = db.UpdateValidatorWrapper(wrapper.Address, wrapper) |
||||
err = ctx2.Delegate(db, &delegate) |
||||
if err != nil { |
||||
t.Errorf("Got error %v in Delegate", err) |
||||
} |
||||
|
||||
// undelegate test
|
||||
undelegate := sampleUndelegate(*key) |
||||
err = ctx.Undelegate(db, &undelegate) |
||||
if err != nil { |
||||
t.Errorf("Got error %v in Undelegate", err) |
||||
} |
||||
|
||||
// collectRewards test
|
||||
collectRewards := sampleCollectRewards(*key) |
||||
// add block rewards to make sure there are some to collect
|
||||
wrapper.Delegations[0].Undelegations = []staking.Undelegation{} |
||||
wrapper.Delegations[0].Reward = common.Big257 |
||||
db.UpdateValidatorWrapper(wrapper.Address, wrapper) |
||||
err = ctx.CollectRewards(db, &collectRewards) |
||||
if err != nil { |
||||
t.Errorf("Got error %v in CollectRewards", err) |
||||
} |
||||
|
||||
// migration test - when from has no delegations
|
||||
toKey, _ := crypto.GenerateKey() |
||||
migration := sampleMigrationMsg(*toKey, *key) |
||||
delegates, err := ctx.MigrateDelegations(db, &migration) |
||||
expectedError := errors.New("No delegations to migrate") |
||||
if err != nil && expectedError.Error() != err.Error() { |
||||
t.Errorf("Got error %v in MigrateDelegations but expected %v", err, expectedError) |
||||
} |
||||
if len(delegates) > 0 { |
||||
t.Errorf("Got delegates to migrate when none were expected") |
||||
} |
||||
// migration test - when from == to
|
||||
migration = sampleMigrationMsg(*toKey, *toKey) |
||||
delegates, err = ctx.MigrateDelegations(db, &migration) |
||||
expectedError = errors.New("From and To are the same address") |
||||
if err != nil && expectedError.Error() != err.Error() { |
||||
t.Errorf("Got error %v in MigrateDelegations but expected %v", err, expectedError) |
||||
} |
||||
if len(delegates) > 0 { |
||||
t.Errorf("Got delegates to migrate when none were expected") |
||||
} |
||||
// migration test - when `to` has no delegations
|
||||
snapshot := db.Snapshot() |
||||
migration = sampleMigrationMsg(*key, *toKey) |
||||
wrapper, _ = db.ValidatorWrapper(wrapper.Address, true, false) |
||||
expectedAmount := wrapper.Delegations[0].Amount |
||||
delegates, err = ctx.MigrateDelegations(db, &migration) |
||||
if err != nil { |
||||
t.Errorf("Got error %v in MigrateDelegations", err) |
||||
} |
||||
if len(delegates) != 1 { |
||||
t.Errorf("Got %d delegations to migrate, expected just 1", len(delegates)) |
||||
} |
||||
wrapper, _ = db.ValidatorWrapper(createValidator.ValidatorAddress, false, true) |
||||
if wrapper.Delegations[0].Amount.Cmp(common.Big0) != 0 { |
||||
t.Errorf("Expected delegation at index 0 to have amount 0, but it has amount %d", |
||||
wrapper.Delegations[0].Amount) |
||||
} |
||||
if wrapper.Delegations[1].Amount.Cmp(expectedAmount) != 0 { |
||||
t.Errorf("Expected delegation at index 1 to have amount %d, but it has amount %d", |
||||
expectedAmount, wrapper.Delegations[1].Amount) |
||||
} |
||||
db.RevertToSnapshot(snapshot) |
||||
snapshot = db.Snapshot() |
||||
// migration test - when `from` delegation amount = 0 and no undelegations
|
||||
wrapper, _ = db.ValidatorWrapper(createValidator.ValidatorAddress, false, true) |
||||
wrapper.Delegations[0].Undelegations = make([]staking.Undelegation, 0) |
||||
wrapper.Delegations[0].Amount = common.Big0 |
||||
wrapper.Status = effective.Inactive |
||||
err = db.UpdateValidatorWrapperWithRevert(wrapper.Address, wrapper) |
||||
if err != nil { |
||||
t.Errorf("Got error %v in UpdateValidatorWrapperWithRevert", err) |
||||
} |
||||
delegates, err = ctx.MigrateDelegations(db, &migration) |
||||
if err != nil { |
||||
t.Errorf("Got error %v in MigrateDelegations", err) |
||||
} |
||||
if len(delegates) != 0 { |
||||
t.Errorf("Got %d delegations to migrate, expected none", len(delegates)) |
||||
} |
||||
db.RevertToSnapshot(snapshot) |
||||
snapshot = db.Snapshot() |
||||
// migration test - when `to` has one delegation
|
||||
wrapper, _ = db.ValidatorWrapper(createValidator.ValidatorAddress, false, true) |
||||
wrapper.Delegations = append(wrapper.Delegations, staking.NewDelegation( |
||||
migration.To, new(big.Int).Mul(big.NewInt(denominations.One), big.NewInt(100)))) |
||||
expectedAmount = big.NewInt(0).Add( |
||||
wrapper.Delegations[0].Amount, wrapper.Delegations[1].Amount, |
||||
) |
||||
err = db.UpdateValidatorWrapperWithRevert(wrapper.Address, wrapper) |
||||
if err != nil { |
||||
t.Errorf("Got error %v in UpdateValidatorWrapperWithRevert", err) |
||||
} |
||||
delegates, err = ctx.MigrateDelegations(db, &migration) |
||||
if err != nil { |
||||
t.Errorf("Got error %v in MigrateDelegations", err) |
||||
} |
||||
if len(delegates) != 1 { |
||||
t.Errorf("Got %d delegations to migrate, expected just 1", len(delegates)) |
||||
} |
||||
// read from updated wrapper
|
||||
wrapper, _ = db.ValidatorWrapper(createValidator.ValidatorAddress, true, false) |
||||
if wrapper.Delegations[0].Amount.Cmp(common.Big0) != 0 { |
||||
t.Errorf("Expected delegation at index 0 to have amount 0, but it has amount %d", |
||||
wrapper.Delegations[0].Amount) |
||||
} |
||||
if wrapper.Delegations[1].Amount.Cmp(expectedAmount) != 0 { |
||||
t.Errorf("Expected delegation at index 1 to have amount %d, but it has amount %d", |
||||
expectedAmount, wrapper.Delegations[1].Amount) |
||||
} |
||||
db.RevertToSnapshot(snapshot) |
||||
snapshot = db.Snapshot() |
||||
// migration test - when `to` has one undelegation in the current epoch and so does `from`
|
||||
wrapper, _ = db.ValidatorWrapper(createValidator.ValidatorAddress, false, true) |
||||
delegation := staking.NewDelegation(migration.To, big.NewInt(0)) |
||||
delegation.Undelegations = []staking.Undelegation{ |
||||
staking.Undelegation{ |
||||
Amount: new(big.Int).Mul(big.NewInt(denominations.One), big.NewInt(100)), |
||||
Epoch: common.Big0, |
||||
}, |
||||
} |
||||
wrapper.Delegations[0].Undelegate( |
||||
big.NewInt(0), new(big.Int).Mul(big.NewInt(denominations.One), big.NewInt(100)), |
||||
) |
||||
expectedAmount = big.NewInt(0).Add( |
||||
wrapper.Delegations[0].Amount, big.NewInt(0), |
||||
) |
||||
wrapper.Delegations = append(wrapper.Delegations, delegation) |
||||
expectedDelegations := []staking.Delegation{ |
||||
staking.Delegation{ |
||||
DelegatorAddress: wrapper.Address, |
||||
Amount: big.NewInt(0), |
||||
Reward: big.NewInt(0), |
||||
Undelegations: []staking.Undelegation{}, |
||||
}, |
||||
staking.Delegation{ |
||||
DelegatorAddress: crypto.PubkeyToAddress(toKey.PublicKey), |
||||
Amount: expectedAmount, |
||||
Undelegations: []staking.Undelegation{ |
||||
staking.Undelegation{ |
||||
Amount: new(big.Int).Mul(big.NewInt(denominations.One), big.NewInt(200)), |
||||
Epoch: common.Big0, |
||||
}, |
||||
}, |
||||
}, |
||||
} |
||||
err = db.UpdateValidatorWrapperWithRevert(wrapper.Address, wrapper) |
||||
if err != nil { |
||||
t.Errorf("Got error %v in UpdateValidatorWrapperWithRevert", err) |
||||
} |
||||
delegates, err = ctx.MigrateDelegations(db, &migration) |
||||
if err != nil { |
||||
t.Errorf("Got error %v in MigrateDelegations", err) |
||||
} |
||||
if len(delegates) != 1 { |
||||
t.Errorf("Got %d delegations to migrate, expected just 1", len(delegates)) |
||||
} |
||||
// read from updated wrapper
|
||||
wrapper, _ = db.ValidatorWrapper(createValidator.ValidatorAddress, true, false) |
||||
// and finally the check for delegations being equal
|
||||
if err := staketest.CheckDelegationsEqual( |
||||
wrapper.Delegations, |
||||
expectedDelegations, |
||||
); err != nil { |
||||
t.Errorf("Got error %s in CheckDelegationsEqual", err) |
||||
} |
||||
|
||||
// test for migration gas
|
||||
db.RevertToSnapshot(snapshot) |
||||
// to calculate gas we need to test 0 / 1 / 2 delegations to migrate as per ReadDelegationsByDelegator
|
||||
// case 0
|
||||
evm := vm.NewEVM(ctx, db, params.TestChainConfig, vm.Config{}) |
||||
gas, err := ctx.CalculateMigrationGas(db, &staking.MigrationMsg{ |
||||
From: crypto.PubkeyToAddress(toKey.PublicKey), |
||||
To: crypto.PubkeyToAddress(key.PublicKey), |
||||
}, evm.ChainConfig().IsS3(evm.EpochNumber), evm.ChainConfig().IsS3(evm.EpochNumber)) |
||||
var expectedGasMin uint64 = params.TxGas |
||||
if err != nil { |
||||
t.Errorf("Gas error %s", err) |
||||
} |
||||
if gas < expectedGasMin { |
||||
t.Errorf("Gas for 0 migration was expected to be at least %d but got %d", expectedGasMin, gas) |
||||
} |
||||
// case 1
|
||||
gas, err = ctx.CalculateMigrationGas(db, &migration, |
||||
evm.ChainConfig().IsS3(evm.EpochNumber), evm.ChainConfig().IsS3(evm.EpochNumber)) |
||||
expectedGasMin = params.TxGas |
||||
if err != nil { |
||||
t.Errorf("Gas error %s", err) |
||||
} |
||||
if gas < expectedGasMin { |
||||
t.Errorf("Gas for 1 migration was expected to be at least %d but got %d", expectedGasMin, gas) |
||||
} |
||||
// case 2
|
||||
createValidator = sampleCreateValidator(*toKey) |
||||
db.AddBalance(createValidator.ValidatorAddress, createValidator.Amount) |
||||
err = ctx.CreateValidator(db, &createValidator) |
||||
delegate = sampleDelegate(*toKey) |
||||
delegate.DelegatorAddress = crypto.PubkeyToAddress(key.PublicKey) |
||||
_ = ctx.Delegate(db, &delegate) |
||||
delegationIndex := staking.DelegationIndex{ |
||||
ValidatorAddress: crypto.PubkeyToAddress(toKey.PublicKey), |
||||
Index: uint64(1), |
||||
BlockNum: common.Big0, |
||||
} |
||||
err = chain.writeDelegationsByDelegator(batch, migration.From, []staking.DelegationIndex{selfIndex, delegationIndex}) |
||||
gas, err = ctx.CalculateMigrationGas(db, &migration, |
||||
evm.ChainConfig().IsS3(evm.EpochNumber), evm.ChainConfig().IsS3(evm.EpochNumber)) |
||||
expectedGasMin = 2 * params.TxGas |
||||
if err != nil { |
||||
t.Errorf("Gas error %s", err) |
||||
} |
||||
if gas < expectedGasMin { |
||||
t.Errorf("Gas for 2 migrations was expected to be at least %d but got %d", expectedGasMin, gas) |
||||
} |
||||
} |
||||
|
||||
func generateBLSKeyAndSig() (bls.SerializedPublicKey, bls.SerializedSignature) { |
||||
p := &bls_core.PublicKey{} |
||||
p.DeserializeHexStr(testBLSPubKey) |
||||
pub := bls.SerializedPublicKey{} |
||||
pub.FromLibBLSPublicKey(p) |
||||
messageBytes := []byte(staking.BLSVerificationStr) |
||||
privateKey := &bls_core.SecretKey{} |
||||
privateKey.DeserializeHexStr(testBLSPrvKey) |
||||
msgHash := hash.Keccak256(messageBytes) |
||||
signature := privateKey.SignHash(msgHash[:]) |
||||
var sig bls.SerializedSignature |
||||
copy(sig[:], signature.Serialize()) |
||||
return pub, sig |
||||
} |
||||
|
||||
func sampleCreateValidator(key ecdsa.PrivateKey) staking.CreateValidator { |
||||
pub, sig := generateBLSKeyAndSig() |
||||
|
||||
ra, _ := numeric.NewDecFromStr("0.7") |
||||
maxRate, _ := numeric.NewDecFromStr("1") |
||||
maxChangeRate, _ := numeric.NewDecFromStr("0.5") |
||||
return staking.CreateValidator{ |
||||
Description: staking.Description{ |
||||
Name: "SuperHero", |
||||
Identity: "YouWouldNotKnow", |
||||
Website: "Secret Website", |
||||
SecurityContact: "LicenseToKill", |
||||
Details: "blah blah blah", |
||||
}, |
||||
CommissionRates: staking.CommissionRates{ |
||||
Rate: ra, |
||||
MaxRate: maxRate, |
||||
MaxChangeRate: maxChangeRate, |
||||
}, |
||||
MinSelfDelegation: new(big.Int).Mul(big.NewInt(denominations.One), big.NewInt(10000)), |
||||
MaxTotalDelegation: new(big.Int).Mul(big.NewInt(denominations.One), big.NewInt(20000)), |
||||
ValidatorAddress: crypto.PubkeyToAddress(key.PublicKey), |
||||
SlotPubKeys: []bls.SerializedPublicKey{pub}, |
||||
SlotKeySigs: []bls.SerializedSignature{sig}, |
||||
Amount: new(big.Int).Mul(big.NewInt(denominations.One), big.NewInt(15000)), |
||||
} |
||||
} |
||||
|
||||
func sampleEditValidator(key ecdsa.PrivateKey) staking.EditValidator { |
||||
// generate new key and sig
|
||||
slotKeyToAdd, slotKeyToAddSig := generateBLSKeyAndSig() |
||||
|
||||
// rate
|
||||
ra, _ := numeric.NewDecFromStr("0.8") |
||||
|
||||
return staking.EditValidator{ |
||||
Description: staking.Description{ |
||||
Name: "Alice", |
||||
Identity: "alice", |
||||
Website: "alice.harmony.one", |
||||
SecurityContact: "Bob", |
||||
Details: "Don't mess with me!!!", |
||||
}, |
||||
CommissionRate: &ra, |
||||
MinSelfDelegation: new(big.Int).Mul(big.NewInt(denominations.One), big.NewInt(10000)), |
||||
MaxTotalDelegation: new(big.Int).Mul(big.NewInt(denominations.One), big.NewInt(20000)), |
||||
SlotKeyToRemove: nil, |
||||
SlotKeyToAdd: &slotKeyToAdd, |
||||
SlotKeyToAddSig: &slotKeyToAddSig, |
||||
ValidatorAddress: crypto.PubkeyToAddress(key.PublicKey), |
||||
} |
||||
} |
||||
|
||||
func sampleDelegate(key ecdsa.PrivateKey) staking.Delegate { |
||||
address := crypto.PubkeyToAddress(key.PublicKey) |
||||
return staking.Delegate{ |
||||
DelegatorAddress: address, |
||||
ValidatorAddress: address, |
||||
// additional delegation of 1000 ONE
|
||||
Amount: new(big.Int).Mul(big.NewInt(denominations.One), big.NewInt(1000)), |
||||
} |
||||
} |
||||
|
||||
func sampleUndelegate(key ecdsa.PrivateKey) staking.Undelegate { |
||||
address := crypto.PubkeyToAddress(key.PublicKey) |
||||
return staking.Undelegate{ |
||||
DelegatorAddress: address, |
||||
ValidatorAddress: address, |
||||
// undelegate the delegation of 1000 ONE
|
||||
Amount: new(big.Int).Mul(big.NewInt(denominations.One), big.NewInt(1000)), |
||||
} |
||||
} |
||||
|
||||
func sampleCollectRewards(key ecdsa.PrivateKey) staking.CollectRewards { |
||||
address := crypto.PubkeyToAddress(key.PublicKey) |
||||
return staking.CollectRewards{ |
||||
DelegatorAddress: address, |
||||
} |
||||
} |
||||
|
||||
func sampleMigrationMsg(from ecdsa.PrivateKey, to ecdsa.PrivateKey) staking.MigrationMsg { |
||||
fromAddress := crypto.PubkeyToAddress(from.PublicKey) |
||||
toAddress := crypto.PubkeyToAddress(to.PublicKey) |
||||
return staking.MigrationMsg{ |
||||
From: fromAddress, |
||||
To: toAddress, |
||||
} |
||||
} |
||||
|
||||
func TestWriteCapablePrecompilesIntegration(t *testing.T) { |
||||
key, _ := crypto.GenerateKey() |
||||
chain, db, header, _ := getTestEnvironment(*key) |
||||
// gp := new(GasPool).AddGas(math.MaxUint64)
|
||||
tx := types.NewTransaction(1, common.BytesToAddress([]byte{0x11}), 0, big.NewInt(111), 1111, big.NewInt(11111), []byte{0x11, 0x11, 0x11}) |
||||
msg, _ := tx.AsMessage(types.NewEIP155Signer(common.Big2)) |
||||
ctx := NewEVMContext(msg, header, chain, nil /* coinbase */) |
||||
evm := vm.NewEVM(ctx, db, params.TestChainConfig, vm.Config{}) |
||||
// interpreter := vm.NewEVMInterpreter(evm, vm.Config{})
|
||||
address := common.BytesToAddress([]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, 0, 252}) |
||||
// caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int)
|
||||
_, _, err := evm.Call(vm.AccountRef(common.Address{}), address, |
||||
[]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}, |
||||
math.MaxUint64, new(big.Int)) |
||||
expectedError := errors.New("abi: cannot marshal in to go type: length insufficient 31 require 32") |
||||
if err != nil { |
||||
if err.Error() != expectedError.Error() { |
||||
t.Errorf(fmt.Sprintf("Got error %v in evm.Call but expected %v", err, expectedError)) |
||||
} |
||||
} |
||||
|
||||
// now add a validator, and send its address as caller
|
||||
createValidator := sampleCreateValidator(*key) |
||||
err = ctx.CreateValidator(db, &createValidator) |
||||
_, _, err = evm.Call(vm.AccountRef(common.Address{}), |
||||
createValidator.ValidatorAddress, |
||||
[]byte{}, |
||||
math.MaxUint64, new(big.Int)) |
||||
if err != nil { |
||||
t.Errorf(fmt.Sprintf("Got error %v in evm.Call", err)) |
||||
} |
||||
|
||||
// now without staking precompile
|
||||
cfg := params.TestChainConfig |
||||
cfg.StakingPrecompileEpoch = big.NewInt(10000000) |
||||
evm = vm.NewEVM(ctx, db, cfg, vm.Config{}) |
||||
_, _, err = evm.Call(vm.AccountRef(common.Address{}), |
||||
createValidator.ValidatorAddress, |
||||
[]byte{}, |
||||
math.MaxUint64, new(big.Int)) |
||||
if err != nil { |
||||
t.Errorf(fmt.Sprintf("Got error %v in evm.Call", err)) |
||||
} |
||||
} |
@ -0,0 +1,83 @@ |
||||
package core |
||||
|
||||
import ( |
||||
"crypto/ecdsa" |
||||
"fmt" |
||||
"math" |
||||
"testing" |
||||
|
||||
"github.com/ethereum/go-ethereum/crypto" |
||||
"github.com/harmony-one/harmony/core/vm" |
||||
"github.com/harmony-one/harmony/internal/params" |
||||
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, chain) |
||||
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)) |
||||
} |
||||
} |
||||
}) |
||||
} |
@ -0,0 +1,145 @@ |
||||
package vm |
||||
|
||||
import ( |
||||
"errors" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/rlp" |
||||
"github.com/harmony-one/harmony/shard" |
||||
"github.com/harmony-one/harmony/staking" |
||||
stakingTypes "github.com/harmony-one/harmony/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{}, |
||||
} |
||||
|
||||
// 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 |
||||
} |
||||
if delegate, ok := stakeMsg.(*stakingTypes.Delegate); ok { |
||||
if err := evm.Delegate(evm.StateDB, 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, undelegate) |
||||
} |
||||
if collectRewards, ok := stakeMsg.(*stakingTypes.CollectRewards); ok { |
||||
return nil, evm.CollectRewards(evm.StateDB, collectRewards) |
||||
} |
||||
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") |
||||
} |
@ -0,0 +1,213 @@ |
||||
package vm |
||||
|
||||
import ( |
||||
"bytes" |
||||
"errors" |
||||
"fmt" |
||||
"math/big" |
||||
"testing" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/harmony-one/harmony/internal/params" |
||||
stakingTypes "github.com/harmony-one/harmony/staking/types" |
||||
) |
||||
|
||||
type writeCapablePrecompileTest struct { |
||||
input, expected []byte |
||||
name string |
||||
expectedError error |
||||
p *WriteCapablePrecompiledContract |
||||
} |
||||
|
||||
func CollectRewardsFn() CollectRewardsFunc { |
||||
return func(db StateDB, collectRewards *stakingTypes.CollectRewards) error { |
||||
return nil |
||||
} |
||||
} |
||||
|
||||
func DelegateFn() DelegateFunc { |
||||
return func(db StateDB, delegate *stakingTypes.Delegate) error { |
||||
return nil |
||||
} |
||||
} |
||||
|
||||
func UndelegateFn() UndelegateFunc { |
||||
return func(db StateDB, undelegate *stakingTypes.Undelegate) error { |
||||
return nil |
||||
} |
||||
} |
||||
|
||||
func CreateValidatorFn() CreateValidatorFunc { |
||||
return func(db StateDB, createValidator *stakingTypes.CreateValidator) error { |
||||
return nil |
||||
} |
||||
} |
||||
|
||||
func EditValidatorFn() EditValidatorFunc { |
||||
return func(db StateDB, editValidator *stakingTypes.EditValidator) error { |
||||
return nil |
||||
} |
||||
} |
||||
|
||||
func MigrateDelegationsFn() MigrateDelegationsFunc { |
||||
return func(db StateDB, migrationMsg *stakingTypes.MigrationMsg) ([]interface{}, error) { |
||||
return nil, nil |
||||
} |
||||
} |
||||
|
||||
func CalculateMigrationGasFn() CalculateMigrationGasFunc { |
||||
return func(db StateDB, migrationMsg *stakingTypes.MigrationMsg, homestead bool, istanbul bool) (uint64, error) { |
||||
return 0, nil |
||||
} |
||||
} |
||||
|
||||
func testStakingPrecompile(test writeCapablePrecompileTest, t *testing.T) { |
||||
var env = NewEVM(Context{CollectRewards: CollectRewardsFn(), |
||||
Delegate: DelegateFn(), |
||||
Undelegate: UndelegateFn(), |
||||
CreateValidator: CreateValidatorFn(), |
||||
EditValidator: EditValidatorFn(), |
||||
ShardID: 0, |
||||
MigrateDelegations: MigrateDelegationsFn(), |
||||
CalculateMigrationGas: CalculateMigrationGasFn(), |
||||
}, nil, params.TestChainConfig, Config{}) |
||||
// use required gas to avoid out of gas errors
|
||||
p := &stakingPrecompile{} |
||||
t.Run(fmt.Sprintf("%s", test.name), func(t *testing.T) { |
||||
contract := NewContract(AccountRef(common.HexToAddress("1337")), AccountRef(common.HexToAddress("1338")), new(big.Int), 0) |
||||
gas, err := p.RequiredGas(env, contract, test.input) |
||||
if err != nil { |
||||
t.Error(err) |
||||
} |
||||
contract.Gas = gas |
||||
if res, err := RunWriteCapablePrecompiledContract(p, env, contract, test.input, false); 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 bytes.Compare(res, test.expected) != 0 { |
||||
t.Errorf("Expected %v, got %v", test.expected, res) |
||||
} |
||||
} |
||||
}) |
||||
} |
||||
|
||||
func TestStakingPrecompiles(t *testing.T) { |
||||
for _, test := range StakingPrecompileTests { |
||||
testStakingPrecompile(test, t) |
||||
} |
||||
} |
||||
|
||||
func TestWriteCapablePrecompilesReadOnly(t *testing.T) { |
||||
p := &stakingPrecompile{} |
||||
expectedError := errWriteProtection |
||||
res, err := RunWriteCapablePrecompiledContract(p, nil, nil, []byte{}, true) |
||||
if err != nil { |
||||
if err.Error() != expectedError.Error() { |
||||
t.Errorf("Expected error %v, got %v", expectedError, err) |
||||
} |
||||
} else { |
||||
t.Errorf("Expected an error %v but instead got result %v", expectedError, res) |
||||
} |
||||
} |
||||
|
||||
var StakingPrecompileTests = []writeCapablePrecompileTest{ |
||||
{ |
||||
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: nil, |
||||
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: nil, |
||||
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: nil, |
||||
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{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: nil, |
||||
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", |
||||
}, |
||||
} |
@ -0,0 +1,35 @@ |
||||
package vm |
||||
|
||||
import ( |
||||
"math/big" |
||||
"testing" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/harmony-one/harmony/internal/params" |
||||
) |
||||
|
||||
// this test is here so we can cover the input = epoch.bytes() line as well
|
||||
func TestEpochPrecompile(t *testing.T) { |
||||
targetEpoch := big.NewInt(1) |
||||
evm := NewEVM(Context{ |
||||
EpochNumber: targetEpoch, |
||||
}, nil, params.TestChainConfig, Config{}) |
||||
input := []byte{} |
||||
precompileAddr := common.BytesToAddress([]byte{251}) |
||||
contract := Contract{ |
||||
CodeAddr: &precompileAddr, |
||||
Gas: GasQuickStep, |
||||
} |
||||
result, err := run(evm, |
||||
&contract, |
||||
input, |
||||
true, |
||||
) |
||||
if err != nil { |
||||
t.Fatalf("Got error%v\n", err) |
||||
} |
||||
resultingEpoch := new(big.Int).SetBytes(result) |
||||
if resultingEpoch.Cmp(targetEpoch) != 0 { |
||||
t.Error("Epoch did not match") |
||||
} |
||||
} |
@ -0,0 +1,233 @@ |
||||
package staking |
||||
|
||||
import ( |
||||
"bytes" |
||||
"math/big" |
||||
"strings" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/harmony-one/harmony/accounts/abi" |
||||
stakingTypes "github.com/harmony-one/harmony/staking/types" |
||||
"github.com/pkg/errors" |
||||
) |
||||
|
||||
var abiStaking abi.ABI |
||||
|
||||
func init() { |
||||
// for commission rates => solidity does not support floats directly
|
||||
// so send commission rates as string
|
||||
StakingABIJSON := ` |
||||
[ |
||||
{ |
||||
"inputs": [ |
||||
{ |
||||
"internalType": "address", |
||||
"name": "delegatorAddress", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"name": "CollectRewards", |
||||
"outputs": [], |
||||
"stateMutability": "nonpayable", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"inputs": [ |
||||
{ |
||||
"internalType": "address", |
||||
"name": "delegatorAddress", |
||||
"type": "address" |
||||
}, |
||||
{ |
||||
"internalType": "address", |
||||
"name": "validatorAddress", |
||||
"type": "address" |
||||
}, |
||||
{ |
||||
"internalType": "uint256", |
||||
"name": "amount", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"name": "Delegate", |
||||
"outputs": [], |
||||
"stateMutability": "nonpayable", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"inputs": [ |
||||
{ |
||||
"internalType": "address", |
||||
"name": "delegatorAddress", |
||||
"type": "address" |
||||
}, |
||||
{ |
||||
"internalType": "address", |
||||
"name": "validatorAddress", |
||||
"type": "address" |
||||
}, |
||||
{ |
||||
"internalType": "uint256", |
||||
"name": "amount", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"name": "Undelegate", |
||||
"outputs": [], |
||||
"stateMutability": "nonpayable", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"inputs": [ |
||||
{ |
||||
"internalType": "address", |
||||
"name": "from", |
||||
"type": "address" |
||||
}, |
||||
{ |
||||
"internalType": "address", |
||||
"name": "to", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"name": "Migrate", |
||||
"outputs": [], |
||||
"stateMutability": "nonpayable", |
||||
"type": "function" |
||||
} |
||||
] |
||||
` |
||||
abiStaking, _ = abi.JSON(strings.NewReader(StakingABIJSON)) |
||||
} |
||||
|
||||
// contractCaller (and not Contract) is used here to avoid import cycle
|
||||
func ParseStakeMsg(contractCaller common.Address, input []byte) (interface{}, error) { |
||||
method, err := abiStaking.MethodById(input) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
input = input[4:] // drop the method selector
|
||||
args := map[string]interface{}{} // store into map
|
||||
if err = method.Inputs.UnpackIntoMap(args, input); err != nil { |
||||
return nil, err |
||||
} |
||||
switch method.Name { |
||||
case "Delegate": |
||||
{ |
||||
// in case of assembly call, a contract will delegate its own balance
|
||||
// in case of assembly delegatecall, contract.Caller() is msg.sender
|
||||
// which means an EOA can
|
||||
// (1) deploy a contract which receives delegations and amounts
|
||||
// (2) call the contract, which then performs the tx on behalf of the EOA
|
||||
address, err := ValidateContractAddress(contractCaller, args, "delegatorAddress") |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
validatorAddress, err := ParseAddressFromKey(args, "validatorAddress") |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
amount, err := ParseBigIntFromKey(args, "amount") |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
stakeMsg := &stakingTypes.Delegate{ |
||||
DelegatorAddress: address, |
||||
ValidatorAddress: validatorAddress, |
||||
Amount: amount, |
||||
} |
||||
return stakeMsg, nil |
||||
} |
||||
case "Undelegate": |
||||
{ |
||||
// same validation as above
|
||||
address, err := ValidateContractAddress(contractCaller, args, "delegatorAddress") |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
validatorAddress, err := ParseAddressFromKey(args, "validatorAddress") |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
// this type assertion is needed by Golang
|
||||
amount, err := ParseBigIntFromKey(args, "amount") |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
stakeMsg := &stakingTypes.Undelegate{ |
||||
DelegatorAddress: address, |
||||
ValidatorAddress: validatorAddress, |
||||
Amount: amount, |
||||
} |
||||
return stakeMsg, nil |
||||
} |
||||
case "CollectRewards": |
||||
{ |
||||
// same validation as above
|
||||
address, err := ValidateContractAddress(contractCaller, args, "delegatorAddress") |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
stakeMsg := &stakingTypes.CollectRewards{ |
||||
DelegatorAddress: address, |
||||
} |
||||
return stakeMsg, nil |
||||
} |
||||
case "Migrate": |
||||
{ |
||||
from, err := ValidateContractAddress(contractCaller, args, "from") |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
to, err := ParseAddressFromKey(args, "to") |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
// no sanity check for migrating to same address, just do nothing
|
||||
return &stakingTypes.MigrationMsg{ |
||||
From: from, |
||||
To: to, |
||||
}, nil |
||||
} |
||||
default: |
||||
{ |
||||
return nil, errors.New("[StakingPrecompile] Invalid method name from ABI selector") |
||||
} |
||||
} |
||||
} |
||||
|
||||
// used to ensure caller == delegatorAddress
|
||||
func ValidateContractAddress(contractCaller common.Address, args map[string]interface{}, key string) (common.Address, error) { |
||||
address, err := ParseAddressFromKey(args, key) |
||||
if err != nil { |
||||
return common.Address{}, err |
||||
} |
||||
if !bytes.Equal(contractCaller.Bytes(), address.Bytes()) { |
||||
return common.Address{}, errors.Errorf( |
||||
"[StakingPrecompile] Address mismatch, expected %s have %s", |
||||
contractCaller.String(), address.String(), |
||||
) |
||||
} else { |
||||
return address, nil |
||||
} |
||||
} |
||||
|
||||
// used for both delegatorAddress and validatorAddress
|
||||
func ParseAddressFromKey(args map[string]interface{}, key string) (common.Address, error) { |
||||
if address, ok := args[key].(common.Address); ok { |
||||
return address, nil |
||||
} else { |
||||
return common.Address{}, errors.Errorf("Cannot parse address from %v", args[key]) |
||||
} |
||||
} |
||||
|
||||
// used for amounts
|
||||
func ParseBigIntFromKey(args map[string]interface{}, key string) (*big.Int, error) { |
||||
bigInt, ok := args[key].(*big.Int) |
||||
if !ok { |
||||
return nil, errors.Errorf( |
||||
"Cannot parse BigInt from %v", args[key]) |
||||
} else { |
||||
return bigInt, nil |
||||
} |
||||
} |
@ -0,0 +1,203 @@ |
||||
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) |
||||
} |
||||
} |
Loading…
Reference in new issue