github.com/iotexproject/iotex-core@v1.14.1-rc1/action/stake_reclaim.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/core/types" 15 "github.com/pkg/errors" 16 "google.golang.org/protobuf/proto" 17 18 "github.com/iotexproject/iotex-proto/golang/iotextypes" 19 20 "github.com/iotexproject/iotex-core/pkg/util/byteutil" 21 "github.com/iotexproject/iotex-core/pkg/version" 22 ) 23 24 const ( 25 // ReclaimStakePayloadGas represents the stake reclaim payload gas per uint 26 ReclaimStakePayloadGas = uint64(100) 27 // ReclaimStakeBaseIntrinsicGas represents the base intrinsic gas for stake reclaim 28 ReclaimStakeBaseIntrinsicGas = uint64(10000) 29 30 _reclaimStakeInterfaceABI = `[ 31 { 32 "inputs": [ 33 { 34 "internalType": "uint64", 35 "name": "bucketIndex", 36 "type": "uint64" 37 }, 38 { 39 "internalType": "uint8[]", 40 "name": "data", 41 "type": "uint8[]" 42 } 43 ], 44 "name": "unstake", 45 "outputs": [], 46 "stateMutability": "nonpayable", 47 "type": "function" 48 }, 49 { 50 "inputs": [ 51 { 52 "internalType": "uint64", 53 "name": "bucketIndex", 54 "type": "uint64" 55 }, 56 { 57 "internalType": "uint8[]", 58 "name": "data", 59 "type": "uint8[]" 60 } 61 ], 62 "name": "withdrawStake", 63 "outputs": [], 64 "stateMutability": "nonpayable", 65 "type": "function" 66 } 67 ]` 68 ) 69 70 var ( 71 // _unstakeMethod is the interface of the abi encoding of unstake action 72 _unstakeMethod abi.Method 73 // _withdrawStakeMethod is the interface of the abi encoding of withdrawStake action 74 _withdrawStakeMethod abi.Method 75 _ EthCompatibleAction = (*Unstake)(nil) 76 _ EthCompatibleAction = (*WithdrawStake)(nil) 77 ) 78 79 func init() { 80 reclaimStakeInterface, err := abi.JSON(strings.NewReader(_reclaimStakeInterfaceABI)) 81 if err != nil { 82 panic(err) 83 } 84 var ok bool 85 _unstakeMethod, ok = reclaimStakeInterface.Methods["unstake"] 86 if !ok { 87 panic("fail to load the method") 88 } 89 _withdrawStakeMethod, ok = reclaimStakeInterface.Methods["withdrawStake"] 90 if !ok { 91 panic("fail to load the method") 92 } 93 } 94 95 // reclaimStake defines the action of stake restake/withdraw 96 type reclaimStake struct { 97 AbstractAction 98 99 bucketIndex uint64 100 payload []byte 101 } 102 103 // BucketIndex returns bucket index 104 func (sr *reclaimStake) BucketIndex() uint64 { return sr.bucketIndex } 105 106 // Payload returns the payload bytes 107 func (sr *reclaimStake) Payload() []byte { return sr.payload } 108 109 // Serialize returns a raw byte stream of the stake reclaim action struct 110 func (sr *reclaimStake) Serialize() []byte { 111 return byteutil.Must(proto.Marshal(sr.Proto())) 112 } 113 114 // Proto converts to protobuf stake reclaim action struct 115 func (sr *reclaimStake) Proto() *iotextypes.StakeReclaim { 116 act := &iotextypes.StakeReclaim{ 117 BucketIndex: sr.bucketIndex, 118 Payload: sr.payload, 119 } 120 121 return act 122 } 123 124 // LoadProto converts a protobuf's Action to reclaimStake 125 func (sr *reclaimStake) LoadProto(pbAct *iotextypes.StakeReclaim) error { 126 if pbAct == nil { 127 return ErrNilProto 128 } 129 130 sr.bucketIndex = pbAct.GetBucketIndex() 131 sr.payload = pbAct.GetPayload() 132 return nil 133 } 134 135 // Unstake defines the action of unstake 136 type Unstake struct { 137 reclaimStake 138 } 139 140 // NewUnstake returns a Unstake instance 141 func NewUnstake( 142 nonce uint64, 143 bucketIndex uint64, 144 payload []byte, 145 gasLimit uint64, 146 gasPrice *big.Int, 147 ) (*Unstake, error) { 148 return &Unstake{ 149 reclaimStake{ 150 AbstractAction: AbstractAction{ 151 version: version.ProtocolVersion, 152 nonce: nonce, 153 gasLimit: gasLimit, 154 gasPrice: gasPrice, 155 }, 156 bucketIndex: bucketIndex, 157 payload: payload, 158 }, 159 }, nil 160 } 161 162 // IntrinsicGas returns the intrinsic gas of a Unstake 163 func (su *Unstake) IntrinsicGas() (uint64, error) { 164 payloadSize := uint64(len(su.Payload())) 165 return CalculateIntrinsicGas(ReclaimStakeBaseIntrinsicGas, ReclaimStakePayloadGas, payloadSize) 166 } 167 168 // Cost returns the total cost of a Unstake 169 func (su *Unstake) Cost() (*big.Int, error) { 170 intrinsicGas, err := su.IntrinsicGas() 171 if err != nil { 172 return nil, errors.Wrap(err, "failed to get intrinsic gas for the unstake") 173 } 174 unstakeFee := big.NewInt(0).Mul(su.GasPrice(), big.NewInt(0).SetUint64(intrinsicGas)) 175 return unstakeFee, nil 176 } 177 178 // EncodeABIBinary encodes data in abi encoding 179 func (su *Unstake) EncodeABIBinary() ([]byte, error) { 180 return su.encodeABIBinary() 181 } 182 183 func (su *Unstake) encodeABIBinary() ([]byte, error) { 184 data, err := _unstakeMethod.Inputs.Pack(su.bucketIndex, su.payload) 185 if err != nil { 186 return nil, err 187 } 188 return append(_unstakeMethod.ID, data...), nil 189 } 190 191 // NewUnstakeFromABIBinary decodes data into WithdrawStake action 192 func NewUnstakeFromABIBinary(data []byte) (*Unstake, error) { 193 var ( 194 paramsMap = map[string]interface{}{} 195 ok bool 196 su Unstake 197 ) 198 // sanity check 199 if len(data) <= 4 || !bytes.Equal(_unstakeMethod.ID, data[:4]) { 200 return nil, errDecodeFailure 201 } 202 if err := _unstakeMethod.Inputs.UnpackIntoMap(paramsMap, data[4:]); err != nil { 203 return nil, err 204 } 205 if su.bucketIndex, ok = paramsMap["bucketIndex"].(uint64); !ok { 206 return nil, errDecodeFailure 207 } 208 if su.payload, ok = paramsMap["data"].([]byte); !ok { 209 return nil, errDecodeFailure 210 } 211 return &su, nil 212 } 213 214 // ToEthTx converts action to eth-compatible tx 215 func (su *Unstake) ToEthTx(_ uint32) (*types.Transaction, error) { 216 data, err := su.encodeABIBinary() 217 if err != nil { 218 return nil, err 219 } 220 return types.NewTx(&types.LegacyTx{ 221 Nonce: su.Nonce(), 222 GasPrice: su.GasPrice(), 223 Gas: su.GasLimit(), 224 To: &_stakingProtocolEthAddr, 225 Value: big.NewInt(0), 226 Data: data, 227 }), nil 228 } 229 230 // WithdrawStake defines the action of stake withdraw 231 type WithdrawStake struct { 232 reclaimStake 233 } 234 235 // NewWithdrawStake returns a WithdrawStake instance 236 func NewWithdrawStake( 237 nonce uint64, 238 bucketIndex uint64, 239 payload []byte, 240 gasLimit uint64, 241 gasPrice *big.Int, 242 ) (*WithdrawStake, error) { 243 return &WithdrawStake{ 244 reclaimStake{ 245 AbstractAction: AbstractAction{ 246 version: version.ProtocolVersion, 247 nonce: nonce, 248 gasLimit: gasLimit, 249 gasPrice: gasPrice, 250 }, 251 bucketIndex: bucketIndex, 252 payload: payload, 253 }, 254 }, nil 255 } 256 257 // IntrinsicGas returns the intrinsic gas of a WithdrawStake 258 func (sw *WithdrawStake) IntrinsicGas() (uint64, error) { 259 payloadSize := uint64(len(sw.Payload())) 260 return CalculateIntrinsicGas(ReclaimStakeBaseIntrinsicGas, ReclaimStakePayloadGas, payloadSize) 261 } 262 263 // Cost returns the total cost of a WithdrawStake 264 func (sw *WithdrawStake) Cost() (*big.Int, error) { 265 intrinsicGas, err := sw.IntrinsicGas() 266 if err != nil { 267 return nil, errors.Wrap(err, "failed to get intrinsic gas for the WithdrawStake") 268 } 269 withdrawFee := big.NewInt(0).Mul(sw.GasPrice(), big.NewInt(0).SetUint64(intrinsicGas)) 270 return withdrawFee, nil 271 } 272 273 // EncodeABIBinary encodes data in abi encoding 274 func (sw *WithdrawStake) EncodeABIBinary() ([]byte, error) { 275 return sw.encodeABIBinary() 276 } 277 278 func (sw *WithdrawStake) encodeABIBinary() ([]byte, error) { 279 data, err := _withdrawStakeMethod.Inputs.Pack(sw.bucketIndex, sw.payload) 280 if err != nil { 281 return nil, err 282 } 283 return append(_withdrawStakeMethod.ID, data...), nil 284 } 285 286 // NewWithdrawStakeFromABIBinary decodes data into WithdrawStake action 287 func NewWithdrawStakeFromABIBinary(data []byte) (*WithdrawStake, error) { 288 var ( 289 paramsMap = map[string]interface{}{} 290 ok bool 291 sw WithdrawStake 292 ) 293 // sanity check 294 if len(data) <= 4 || !bytes.Equal(_withdrawStakeMethod.ID, data[:4]) { 295 return nil, errDecodeFailure 296 } 297 if err := _withdrawStakeMethod.Inputs.UnpackIntoMap(paramsMap, data[4:]); err != nil { 298 return nil, err 299 } 300 if sw.bucketIndex, ok = paramsMap["bucketIndex"].(uint64); !ok { 301 return nil, errDecodeFailure 302 } 303 if sw.payload, ok = paramsMap["data"].([]byte); !ok { 304 return nil, errDecodeFailure 305 } 306 return &sw, nil 307 } 308 309 // ToEthTx converts action to eth-compatible tx 310 func (sw *WithdrawStake) ToEthTx(_ uint32) (*types.Transaction, error) { 311 data, err := sw.encodeABIBinary() 312 if err != nil { 313 return nil, err 314 } 315 return types.NewTx(&types.LegacyTx{ 316 Nonce: sw.Nonce(), 317 GasPrice: sw.GasPrice(), 318 Gas: sw.GasLimit(), 319 To: &_stakingProtocolEthAddr, 320 Value: big.NewInt(0), 321 Data: data, 322 }), nil 323 }