Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[action] Transfering delegate ownership, add CandidateTransferOwnership #4236

Merged
merged 14 commits into from
May 22, 2024
3 changes: 3 additions & 0 deletions action/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,9 @@ func newStakingActionFromABIBinary(data []byte) (actionPayload, error) {
if act, err := NewCandidateEndorsementFromABIBinary(data); err == nil {
return act, nil
}
if act, err := NewCandidateTransferOwnershipFromABIBinary(data); err == nil {
return act, nil
}
return nil, ErrInvalidABI
}

Expand Down
206 changes: 206 additions & 0 deletions action/candidate_transfer_ownership.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
package action

import (
"bytes"
"math/big"
"strings"

"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/iotexproject/iotex-address/address"
"github.com/iotexproject/iotex-proto/golang/iotextypes"
"google.golang.org/protobuf/proto"

"github.com/iotexproject/iotex-core/pkg/util/byteutil"
"github.com/iotexproject/iotex-core/pkg/version"
)

const (
// CandidateTransferOwnershipPayloadGas represents the CandidateTransferOwnership payload gas per uint
CandidateTransferOwnershipPayloadGas = uint64(100)
// CandidateTransferOwnershipBaseIntrinsicGas represents the base intrinsic gas for CandidateTransferOwnership
CandidateTransferOwnershipBaseIntrinsicGas = uint64(10000)

_candidateTransferOwnershipInterfaceABI = `[
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
},
{
"internalType": "uint8[]",
"name": "payload",
"type": "uint8[]"
}
],
"name": "candidateTransferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]`
)

var (
// _candidateTransferOwnershipMethod is the interface of the abi encoding of candidate transfer ownership action
_candidateTransferOwnershipMethod abi.Method
)

func init() {
candidateTransferOwnershipInterface, err := abi.JSON(strings.NewReader(_candidateTransferOwnershipInterfaceABI))
if err != nil {
panic(err)
}
var ok bool
_candidateTransferOwnershipMethod, ok = candidateTransferOwnershipInterface.Methods["candidateTransferOwnership"]
if !ok {
panic("fail to load the method")
}
}

// CandidateTransferOwnership is the action to transfer ownership of a candidate
type CandidateTransferOwnership struct {
AbstractAction

newOwner address.Address
payload []byte
millken marked this conversation as resolved.
Show resolved Hide resolved
}

// NewCandidateTransferOwnership returns a CandidateTransferOwnership action
func NewCandidateTransferOwnership(nonce, gasLimit uint64, gasPrice *big.Int,
newOwnerStr string, payload []byte) (*CandidateTransferOwnership, error) {
newOwner, err := address.FromString(newOwnerStr)
if err != nil {
return nil, err
}
return &CandidateTransferOwnership{
AbstractAction: AbstractAction{
version: version.ProtocolVersion,
nonce: nonce,
gasLimit: gasLimit,
gasPrice: gasPrice,
},
newOwner: newOwner,
payload: payload,
}, nil
}

// NewCandidateTransferOwnershipFromAction decode data to CandidateTransferOwnership
func NewCandidateTransferOwnershipFromABIBinary(data []byte) (*CandidateTransferOwnership, error) {
var (
paramsMap = map[string]any{}
ok bool
err error
cr CandidateTransferOwnership
)
// sanity check
if len(data) <= 4 || !bytes.Equal(_candidateTransferOwnershipMethod.ID, data[:4]) {
return nil, errDecodeFailure
}
if err = _candidateTransferOwnershipMethod.Inputs.UnpackIntoMap(paramsMap, data[4:]); err != nil {
return nil, err
}
if cr.newOwner, err = ethAddrToNativeAddr(paramsMap["newOwner"]); err != nil {
return nil, err
}
if cr.payload, ok = paramsMap["payload"].([]byte); !ok {
return nil, errDecodeFailure
}
return &cr, nil
}

// Payload returns the payload bytes
func (act *CandidateTransferOwnership) Payload() []byte { return act.payload }

// NewOwner returns the new owner address
func (act *CandidateTransferOwnership) NewOwner() address.Address { return act.newOwner }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

newOwner may not be a good name for the function and the parameter


// IntrinsicGas returns the intrinsic gas of a CandidateTransferOwnership
func (act *CandidateTransferOwnership) IntrinsicGas() (uint64, error) {
payloadSize := uint64(len(act.Payload()))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

redundant parameter

return CalculateIntrinsicGas(CandidateTransferOwnershipBaseIntrinsicGas, CandidateTransferOwnershipPayloadGas, payloadSize)
}

// Cost returns the total cost of a CandidateTransferOwnership
func (act *CandidateTransferOwnership) Cost() (*big.Int, error) {
intrinsicGas, _ := act.IntrinsicGas()

fee := big.NewInt(0).Mul(act.GasPrice(), big.NewInt(0).SetUint64(intrinsicGas))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

redundant parameter

return fee, nil
}

// Serialize returns a raw byte stream of the CandidateTransferOwnership struct
func (act *CandidateTransferOwnership) Serialize() []byte {
return byteutil.Must(proto.Marshal(act.Proto()))
}

// Proto converts to protobuf CandidateTransferOwnership Action
func (act *CandidateTransferOwnership) Proto() *iotextypes.CandidateTransferOwnership {
ac := iotextypes.CandidateTransferOwnership{
NewOwnerAddress: act.newOwner.String(),
}

if len(act.payload) > 0 {
ac.Payload = make([]byte, len(act.payload))
copy(ac.Payload, act.payload)
}
return &ac
}

// LoadProto loads the CandidateTransferOwnership Action from protobuf
func (act *CandidateTransferOwnership) LoadProto(pbAct *iotextypes.CandidateTransferOwnership) error {
if pbAct == nil {
return ErrNilProto
}
newOwner, err := address.FromString(pbAct.GetNewOwnerAddress())
if err != nil {
return err
}
act.newOwner = newOwner
act.payload = nil
if payload := pbAct.GetPayload(); len(payload) > 0 {
act.payload = make([]byte, len(payload))
copy(act.payload, payload)
}
return nil
}

// EncodeABIBinary encodes data in abi encoding
func (act *CandidateTransferOwnership) EncodeABIBinary() ([]byte, error) {
return act.encodeABIBinary()
}

func (act *CandidateTransferOwnership) encodeABIBinary() ([]byte, error) {
if act.newOwner == nil {
return nil, ErrAddress
}
data, err := _candidateTransferOwnershipMethod.Inputs.Pack(common.BytesToAddress(act.newOwner.Bytes()), act.payload)
if err != nil {
return nil, err
}
return append(_candidateTransferOwnershipMethod.ID, data...), nil
}

// SanityCheck validates the variables in the action
func (act *CandidateTransferOwnership) SanityCheck() error {
return act.AbstractAction.SanityCheck()
}

// ToEthTx returns an Ethereum transaction which corresponds to this action
func (act *CandidateTransferOwnership) ToEthTx(_ uint32) (*types.Transaction, error) {
data, err := act.encodeABIBinary()
if err != nil {
return nil, err
}
return types.NewTx(&types.LegacyTx{
Nonce: act.Nonce(),
GasPrice: act.GasPrice(),
Gas: act.GasLimit(),
To: &_stakingProtocolEthAddr,
Value: big.NewInt(0),
Data: data,
}), nil
}
145 changes: 145 additions & 0 deletions action/candidate_transfer_ownership_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package action

import (
"encoding/hex"
"math/big"
"testing"

"github.com/pkg/errors"
"github.com/stretchr/testify/require"

"github.com/iotexproject/iotex-address/address"

"github.com/iotexproject/iotex-core/pkg/util/byteutil"
)

func TestCandidateTransferOwnership(t *testing.T) {
require := require.New(t)
tests := []struct {
nonce uint64
gasLimit uint64
gasPrice *big.Int
newOwner string
payload []byte
intrinsicGas uint64
cost string
serialize string
expected error
sanityCheck error
}{
// valid test
{
1,
1000000,
big.NewInt(1000),
"io10a298zmzvrt4guq79a9f4x7qedj59y7ery84he",
[]byte("payload"),
10700,
"10700000",
"0a29696f3130613239387a6d7a7672743467757137396139663478377165646a353979376572793834686512077061796c6f6164",
nil,
nil,
},
//invalid address
{
1,
1000000,
big.NewInt(1000),
"ab-10",
[]byte("payload"),
0,
"",
"",
address.ErrInvalidAddr,
nil,
},
//invalid gas price
{
1,
1000000,
big.NewInt(-1000),
"io10a298zmzvrt4guq79a9f4x7qedj59y7ery84he",
nil,
0,
"",
"",
nil,
ErrNegativeValue,
},
}
for _, test := range tests {
cr, err := NewCandidateTransferOwnership(test.nonce, test.gasLimit, test.gasPrice, test.newOwner, test.payload)
require.Equal(test.expected, errors.Cause(err))
if err != nil {
continue
}
err = cr.SanityCheck()
require.Equal(test.sanityCheck, errors.Cause(err))
if err != nil {
continue
}

require.Equal(test.serialize, hex.EncodeToString(cr.Serialize()))

require.NoError(err)
require.Equal(test.gasLimit, cr.GasLimit())
require.Equal(test.gasPrice, cr.GasPrice())
require.Equal(test.nonce, cr.Nonce())

require.Equal(test.newOwner, cr.NewOwner().String())

require.Equal(test.payload, cr.Payload())

gas, err := cr.IntrinsicGas()
require.NoError(err)
require.Equal(test.intrinsicGas, gas)
cost, err := cr.Cost()
require.NoError(err)
require.Equal(test.cost, cost.Text(10))

cr2 := &CandidateTransferOwnership{}
require.NoError(cr2.LoadProto(cr.Proto()))
require.Equal(test.newOwner, cr2.NewOwner().String())
require.Equal(test.payload, cr2.Payload())

}
}

func TestCandidateTransferOwnershipABIEncodeAndDecode(t *testing.T) {
require := require.New(t)
cr, err := NewCandidateTransferOwnership(1, 1000000, big.NewInt(1000), "io10a298zmzvrt4guq79a9f4x7qedj59y7ery84he", []byte("payload"))
require.NoError(err)
enc, err := cr.EncodeABIBinary()
require.NoError(err)

cr2, err := NewCandidateTransferOwnershipFromABIBinary(enc)
require.NoError(err)
require.Equal(cr.NewOwner().String(), cr2.NewOwner().String())
require.Equal(cr.Payload(), cr2.Payload())

cr2.newOwner = nil
enc, err = cr2.EncodeABIBinary()
require.Equal(ErrAddress, errors.Cause(err))
require.Nil(enc)

//invalid data
data := []byte{1, 2, 3, 4}
cr2, err = NewCandidateTransferOwnershipFromABIBinary(data)
require.Equal(errDecodeFailure, err)
require.Nil(cr2)
}

func TestCandidateTransferOwnershipToEthTx(t *testing.T) {
require := require.New(t)
cr, err := NewCandidateTransferOwnership(1, 1000000, big.NewInt(1000), "io10a298zmzvrt4guq79a9f4x7qedj59y7ery84he", []byte("payload"))
require.NoError(err)
ethTx, err := cr.ToEthTx(0)
require.NoError(err)
require.NotNil(ethTx)
require.Equal(byteutil.Must(cr.EncodeABIBinary()), ethTx.Data())
require.Equal(cr.GasPrice(), ethTx.GasPrice())
require.Equal(cr.GasLimit(), ethTx.Gas())
require.Equal(big.NewInt(0), ethTx.Value())
require.Equal(_stakingProtocolEthAddr.Hex(), ethTx.To().Hex())

}
8 changes: 8 additions & 0 deletions action/envelope.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ func (elp *envelope) Proto() *iotextypes.ActionCore {
actCore.Action = &iotextypes.ActionCore_CandidateActivate{CandidateActivate: act.Proto()}
case *CandidateEndorsement:
actCore.Action = &iotextypes.ActionCore_CandidateEndorsement{CandidateEndorsement: act.Proto()}
case *CandidateTransferOwnership:
actCore.Action = &iotextypes.ActionCore_CandidateTransferOwnership{CandidateTransferOwnership: act.Proto()}
default:
log.S().Panicf("Cannot convert type of action %T.\r\n", act)
}
Expand Down Expand Up @@ -225,6 +227,12 @@ func (elp *envelope) LoadProto(pbAct *iotextypes.ActionCore) error {
return err
}
elp.payload = act
case pbAct.GetCandidateTransferOwnership() != nil:
act := &CandidateTransferOwnership{}
if err := act.LoadProto(pbAct.GetCandidateTransferOwnership()); err != nil {
return err
}
elp.payload = act
default:
return errors.Errorf("no applicable action to handle proto type %T", pbAct.Action)
}
Expand Down
Loading
Loading