github.com/iotexproject/iotex-core@v1.14.1-rc1/action/candidate_register.go (about) 1 // Copyright (c) 2020 IoTeX Foundation 2 // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability 3 // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed. 4 // This source code is governed by Apache License 2.0 that can be found in the LICENSE file. 5 6 package action 7 8 import ( 9 "bytes" 10 "math/big" 11 "strings" 12 13 "github.com/ethereum/go-ethereum/accounts/abi" 14 "github.com/ethereum/go-ethereum/common" 15 "github.com/ethereum/go-ethereum/core/types" 16 "github.com/iotexproject/iotex-address/address" 17 "github.com/iotexproject/iotex-proto/golang/iotextypes" 18 "github.com/pkg/errors" 19 "google.golang.org/protobuf/proto" 20 21 "github.com/iotexproject/iotex-core/pkg/util/byteutil" 22 "github.com/iotexproject/iotex-core/pkg/version" 23 ) 24 25 const ( 26 // CandidateRegisterPayloadGas represents the CandidateRegister payload gas per uint 27 CandidateRegisterPayloadGas = uint64(100) 28 // CandidateRegisterBaseIntrinsicGas represents the base intrinsic gas for CandidateRegister 29 CandidateRegisterBaseIntrinsicGas = uint64(10000) 30 31 _candidateRegisterInterfaceABI = `[ 32 { 33 "inputs": [ 34 { 35 "internalType": "string", 36 "name": "name", 37 "type": "string" 38 }, 39 { 40 "internalType": "address", 41 "name": "operatorAddress", 42 "type": "address" 43 }, 44 { 45 "internalType": "address", 46 "name": "rewardAddress", 47 "type": "address" 48 }, 49 { 50 "internalType": "address", 51 "name": "ownerAddress", 52 "type": "address" 53 }, 54 { 55 "internalType": "uint256", 56 "name": "amount", 57 "type": "uint256" 58 }, 59 { 60 "internalType": "uint32", 61 "name": "duration", 62 "type": "uint32" 63 }, 64 { 65 "internalType": "bool", 66 "name": "autoStake", 67 "type": "bool" 68 }, 69 { 70 "internalType": "uint8[]", 71 "name": "data", 72 "type": "uint8[]" 73 } 74 ], 75 "name": "candidateRegister", 76 "outputs": [], 77 "stateMutability": "nonpayable", 78 "type": "function" 79 } 80 ]` 81 ) 82 83 var ( 84 // _candidateRegisterInterface is the interface of the abi encoding of stake action 85 _candidateRegisterMethod abi.Method 86 87 // ErrInvalidAmount represents that amount is 0 or negative 88 ErrInvalidAmount = errors.New("invalid amount") 89 90 //ErrInvalidCanName represents that candidate name is invalid 91 ErrInvalidCanName = errors.New("invalid candidate name") 92 93 _ EthCompatibleAction = (*CandidateRegister)(nil) 94 ) 95 96 // CandidateRegister is the action to register a candidate 97 type CandidateRegister struct { 98 AbstractAction 99 100 name string 101 operatorAddress address.Address 102 rewardAddress address.Address 103 ownerAddress address.Address 104 amount *big.Int 105 duration uint32 106 autoStake bool 107 payload []byte 108 } 109 110 func init() { 111 candidateRegisterInterface, err := abi.JSON(strings.NewReader(_candidateRegisterInterfaceABI)) 112 if err != nil { 113 panic(err) 114 } 115 var ok bool 116 _candidateRegisterMethod, ok = candidateRegisterInterface.Methods["candidateRegister"] 117 if !ok { 118 panic("fail to load the method") 119 } 120 } 121 122 // NewCandidateRegister creates a CandidateRegister instance 123 func NewCandidateRegister( 124 nonce uint64, 125 name, operatorAddrStr, rewardAddrStr, ownerAddrStr, amountStr string, 126 duration uint32, 127 autoStake bool, 128 payload []byte, 129 gasLimit uint64, 130 gasPrice *big.Int, 131 ) (*CandidateRegister, error) { 132 operatorAddr, err := address.FromString(operatorAddrStr) 133 if err != nil { 134 return nil, err 135 } 136 137 rewardAddress, err := address.FromString(rewardAddrStr) 138 if err != nil { 139 return nil, err 140 } 141 142 amount, ok := new(big.Int).SetString(amountStr, 10) 143 if !ok { 144 return nil, errors.Wrapf(ErrInvalidAmount, "amount %s", amount) 145 } 146 147 cr := &CandidateRegister{ 148 AbstractAction: AbstractAction{ 149 version: version.ProtocolVersion, 150 nonce: nonce, 151 gasLimit: gasLimit, 152 gasPrice: gasPrice, 153 }, 154 name: name, 155 operatorAddress: operatorAddr, 156 rewardAddress: rewardAddress, 157 amount: amount, 158 duration: duration, 159 autoStake: autoStake, 160 payload: payload, 161 } 162 163 if len(ownerAddrStr) > 0 { 164 ownerAddress, err := address.FromString(ownerAddrStr) 165 if err != nil { 166 return nil, err 167 } 168 cr.ownerAddress = ownerAddress 169 } 170 return cr, nil 171 } 172 173 // Amount returns the amount 174 func (cr *CandidateRegister) Amount() *big.Int { return cr.amount } 175 176 // Payload returns the payload bytes 177 func (cr *CandidateRegister) Payload() []byte { return cr.payload } 178 179 // Duration returns the self-stake duration 180 func (cr *CandidateRegister) Duration() uint32 { return cr.duration } 181 182 // AutoStake returns the if staking is auth stake 183 func (cr *CandidateRegister) AutoStake() bool { return cr.autoStake } 184 185 // Name returns candidate name to register 186 func (cr *CandidateRegister) Name() string { return cr.name } 187 188 // OperatorAddress returns candidate operatorAddress to register 189 func (cr *CandidateRegister) OperatorAddress() address.Address { return cr.operatorAddress } 190 191 // RewardAddress returns candidate rewardAddress to register 192 func (cr *CandidateRegister) RewardAddress() address.Address { return cr.rewardAddress } 193 194 // OwnerAddress returns candidate ownerAddress to register 195 func (cr *CandidateRegister) OwnerAddress() address.Address { return cr.ownerAddress } 196 197 // Serialize returns a raw byte stream of the CandidateRegister struct 198 func (cr *CandidateRegister) Serialize() []byte { 199 return byteutil.Must(proto.Marshal(cr.Proto())) 200 } 201 202 // Proto converts to protobuf CandidateRegister Action 203 func (cr *CandidateRegister) Proto() *iotextypes.CandidateRegister { 204 act := iotextypes.CandidateRegister{ 205 Candidate: &iotextypes.CandidateBasicInfo{ 206 Name: cr.name, 207 OperatorAddress: cr.operatorAddress.String(), 208 RewardAddress: cr.rewardAddress.String(), 209 }, 210 StakedDuration: cr.duration, 211 AutoStake: cr.autoStake, 212 } 213 214 if cr.amount != nil { 215 act.StakedAmount = cr.amount.String() 216 } 217 218 if cr.ownerAddress != nil { 219 act.OwnerAddress = cr.ownerAddress.String() 220 } 221 222 if len(cr.payload) > 0 { 223 act.Payload = make([]byte, len(cr.payload)) 224 copy(act.Payload, cr.payload) 225 } 226 return &act 227 } 228 229 // LoadProto converts a protobuf's Action to CandidateRegister 230 func (cr *CandidateRegister) LoadProto(pbAct *iotextypes.CandidateRegister) error { 231 if pbAct == nil { 232 return ErrNilProto 233 } 234 235 cInfo := pbAct.GetCandidate() 236 cr.name = cInfo.GetName() 237 238 operatorAddr, err := address.FromString(cInfo.GetOperatorAddress()) 239 if err != nil { 240 return err 241 } 242 rewardAddr, err := address.FromString(cInfo.GetRewardAddress()) 243 if err != nil { 244 return err 245 } 246 247 cr.operatorAddress = operatorAddr 248 cr.rewardAddress = rewardAddr 249 cr.duration = pbAct.GetStakedDuration() 250 cr.autoStake = pbAct.GetAutoStake() 251 252 if len(pbAct.GetStakedAmount()) > 0 { 253 var ok bool 254 if cr.amount, ok = new(big.Int).SetString(pbAct.GetStakedAmount(), 10); !ok { 255 return errors.Errorf("invalid amount %s", pbAct.GetStakedAmount()) 256 } 257 } 258 259 cr.payload = nil 260 if len(pbAct.GetPayload()) > 0 { 261 cr.payload = make([]byte, len(pbAct.GetPayload())) 262 copy(cr.payload, pbAct.GetPayload()) 263 } 264 265 if len(pbAct.GetOwnerAddress()) > 0 { 266 ownerAddr, err := address.FromString(pbAct.GetOwnerAddress()) 267 if err != nil { 268 return err 269 } 270 cr.ownerAddress = ownerAddr 271 } 272 return nil 273 } 274 275 // IntrinsicGas returns the intrinsic gas of a CandidateRegister 276 func (cr *CandidateRegister) IntrinsicGas() (uint64, error) { 277 payloadSize := uint64(len(cr.Payload())) 278 return CalculateIntrinsicGas(CandidateRegisterBaseIntrinsicGas, CandidateRegisterPayloadGas, payloadSize) 279 } 280 281 // Cost returns the total cost of a CandidateRegister 282 func (cr *CandidateRegister) Cost() (*big.Int, error) { 283 intrinsicGas, err := cr.IntrinsicGas() 284 if err != nil { 285 return nil, errors.Wrap(err, "failed to get intrinsic gas for the CandidateRegister creates") 286 } 287 fee := big.NewInt(0).Mul(cr.GasPrice(), big.NewInt(0).SetUint64(intrinsicGas)) 288 return big.NewInt(0).Add(cr.Amount(), fee), nil 289 } 290 291 // SanityCheck validates the variables in the action 292 func (cr *CandidateRegister) SanityCheck() error { 293 if cr.Amount().Sign() < 0 { 294 return errors.Wrap(ErrInvalidAmount, "negative value") 295 } 296 if !IsValidCandidateName(cr.Name()) { 297 return ErrInvalidCanName 298 } 299 300 return cr.AbstractAction.SanityCheck() 301 } 302 303 // EncodeABIBinary encodes data in abi encoding 304 func (cr *CandidateRegister) EncodeABIBinary() ([]byte, error) { 305 return cr.encodeABIBinary() 306 } 307 308 func (cr *CandidateRegister) encodeABIBinary() ([]byte, error) { 309 if cr.operatorAddress == nil { 310 return nil, ErrAddress 311 } 312 if cr.rewardAddress == nil { 313 return nil, ErrAddress 314 } 315 if cr.ownerAddress == nil { 316 return nil, ErrAddress 317 } 318 data, err := _candidateRegisterMethod.Inputs.Pack( 319 cr.name, 320 common.BytesToAddress(cr.operatorAddress.Bytes()), 321 common.BytesToAddress(cr.rewardAddress.Bytes()), 322 common.BytesToAddress(cr.ownerAddress.Bytes()), 323 cr.amount, 324 cr.duration, 325 cr.autoStake, 326 cr.payload) 327 if err != nil { 328 return nil, err 329 } 330 return append(_candidateRegisterMethod.ID, data...), nil 331 } 332 333 // NewCandidateRegisterFromABIBinary decodes data into CandidateRegister action 334 func NewCandidateRegisterFromABIBinary(data []byte) (*CandidateRegister, error) { 335 var ( 336 paramsMap = map[string]interface{}{} 337 ok bool 338 err error 339 cr CandidateRegister 340 ) 341 // sanity check 342 if len(data) <= 4 || !bytes.Equal(_candidateRegisterMethod.ID, data[:4]) { 343 return nil, errDecodeFailure 344 } 345 if err := _candidateRegisterMethod.Inputs.UnpackIntoMap(paramsMap, data[4:]); err != nil { 346 return nil, err 347 } 348 if cr.name, ok = paramsMap["name"].(string); !ok { 349 return nil, errDecodeFailure 350 } 351 if cr.operatorAddress, err = ethAddrToNativeAddr(paramsMap["operatorAddress"]); err != nil { 352 return nil, err 353 } 354 if cr.rewardAddress, err = ethAddrToNativeAddr(paramsMap["rewardAddress"]); err != nil { 355 return nil, err 356 } 357 if cr.ownerAddress, err = ethAddrToNativeAddr(paramsMap["ownerAddress"]); err != nil { 358 return nil, err 359 } 360 if cr.amount, ok = paramsMap["amount"].(*big.Int); !ok { 361 return nil, errDecodeFailure 362 } 363 if cr.duration, ok = paramsMap["duration"].(uint32); !ok { 364 return nil, errDecodeFailure 365 } 366 if cr.autoStake, ok = paramsMap["autoStake"].(bool); !ok { 367 return nil, errDecodeFailure 368 } 369 if cr.payload, ok = paramsMap["data"].([]byte); !ok { 370 return nil, errDecodeFailure 371 } 372 return &cr, nil 373 } 374 375 func ethAddrToNativeAddr(in interface{}) (address.Address, error) { 376 ethAddr, ok := in.(common.Address) 377 if !ok { 378 return nil, errDecodeFailure 379 } 380 return address.FromBytes(ethAddr.Bytes()) 381 } 382 383 // ToEthTx converts action to eth-compatible tx 384 func (cr *CandidateRegister) ToEthTx(_ uint32) (*types.Transaction, error) { 385 data, err := cr.encodeABIBinary() 386 if err != nil { 387 return nil, err 388 } 389 return types.NewTx(&types.LegacyTx{ 390 Nonce: cr.Nonce(), 391 GasPrice: cr.GasPrice(), 392 Gas: cr.GasLimit(), 393 To: &_stakingProtocolEthAddr, 394 Value: big.NewInt(0), 395 Data: data, 396 }), nil 397 } 398 399 // IsValidCandidateName check if a candidate name string is valid. 400 func IsValidCandidateName(s string) bool { 401 if len(s) == 0 || len(s) > 12 { 402 return false 403 } 404 for _, c := range s { 405 if !(('a' <= c && c <= 'z') || ('0' <= c && c <= '9')) { 406 return false 407 } 408 } 409 return true 410 }