github.com/codingfuture/orig-energi3@v0.8.4/energi/api/governance.go (about) 1 // Copyright 2019 The Energi Core Authors 2 // This file is part of the Energi Core library. 3 // 4 // The Energi Core library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The Energi Core library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the Energi Core library. If not, see <http://www.gnu.org/licenses/>. 16 17 package api 18 19 import ( 20 "errors" 21 "math/big" 22 23 "github.com/pborman/uuid" 24 25 "github.com/ethereum/go-ethereum/accounts/abi/bind" 26 "github.com/ethereum/go-ethereum/common" 27 "github.com/ethereum/go-ethereum/common/hexutil" 28 "github.com/ethereum/go-ethereum/log" 29 "github.com/ethereum/go-ethereum/rpc" 30 31 energi_abi "energi.world/core/gen3/energi/abi" 32 energi_common "energi.world/core/gen3/energi/common" 33 energi_params "energi.world/core/gen3/energi/params" 34 ) 35 36 const ( 37 proposalCallGas uint64 = 3000000 38 upgradeCallGas uint64 = 5000000 39 ) 40 41 type GovernanceAPI struct { 42 backend Backend 43 uInfoCache *energi_common.CacheStorage 44 bInfoCache *energi_common.CacheStorage 45 } 46 47 func NewGovernanceAPI(b Backend) *GovernanceAPI { 48 r := &GovernanceAPI{ 49 backend: b, 50 uInfoCache: energi_common.NewCacheStorage(), 51 bInfoCache: energi_common.NewCacheStorage(), 52 } 53 b.OnSyncedHeadUpdates(func() { 54 r.UpgradeInfo() 55 r.BudgetInfo() 56 }) 57 return r 58 } 59 60 //============================================================================= 61 62 func (g *GovernanceAPI) proposal( 63 password *string, 64 owner common.Address, 65 proposal common.Address, 66 ) (session *energi_abi.IProposalSession, err error) { 67 contract, err := energi_abi.NewIProposal(proposal, g.backend.(bind.ContractBackend)) 68 if err != nil { 69 return nil, err 70 } 71 72 session = &energi_abi.IProposalSession{ 73 Contract: contract, 74 CallOpts: bind.CallOpts{ 75 Pending: true, 76 From: owner, 77 GasLimit: energi_params.UnlimitedGas, 78 }, 79 TransactOpts: bind.TransactOpts{ 80 From: owner, 81 Signer: createSignerCallback(g.backend, password), 82 Value: common.Big0, 83 GasLimit: proposalCallGas, 84 }, 85 } 86 return 87 } 88 89 //============================================================================= 90 // VT-5 Voting API 91 //============================================================================= 92 93 func (g *GovernanceAPI) checkCanVote( 94 contract *energi_abi.IProposalSession, 95 mn_owner common.Address, 96 ) error { 97 if can, err := contract.CanVote(mn_owner); err != nil { 98 return err 99 } else if !can { 100 return errors.New("This account is not allowed to vote!") 101 } 102 103 return nil 104 } 105 106 func (g *GovernanceAPI) VoteAccept( 107 proposal common.Address, 108 mn_owner common.Address, 109 password *string, 110 ) (txhash common.Hash, err error) { 111 contract, err := g.proposal(password, mn_owner, proposal) 112 if err != nil { 113 log.Error("Failed", "err", err) 114 return 115 } 116 117 if err = g.checkCanVote(contract, mn_owner); err != nil { 118 log.Error("Failed", "err", err) 119 return 120 } 121 122 tx, err := contract.VoteAccept() 123 124 if tx != nil { 125 txhash = tx.Hash() 126 log.Info("Note: please wait until the proposal TX gets into a block!", "tx", txhash.Hex()) 127 } 128 129 return 130 } 131 132 func (g *GovernanceAPI) VoteReject( 133 proposal common.Address, 134 mn_owner common.Address, 135 password *string, 136 ) (txhash common.Hash, err error) { 137 contract, err := g.proposal(password, mn_owner, proposal) 138 if err != nil { 139 log.Error("Failed", "err", err) 140 return 141 } 142 143 if err = g.checkCanVote(contract, mn_owner); err != nil { 144 log.Error("Failed", "err", err) 145 return 146 } 147 148 tx, err := contract.VoteReject() 149 150 if tx != nil { 151 txhash = tx.Hash() 152 log.Info("Note: please wait until the proposal TX gets into a block!", "tx", txhash.Hex()) 153 } 154 155 return 156 } 157 158 func (g *GovernanceAPI) WithdrawFee( 159 proposal common.Address, 160 payer common.Address, 161 password *string, 162 ) (txhash common.Hash, err error) { 163 contract, err := g.proposal(password, payer, proposal) 164 if err != nil { 165 log.Error("Failed", "err", err) 166 return 167 } 168 169 if res, _ := contract.IsAccepted(); !res { 170 err = errors.New("The proposal is not accepted!") 171 log.Error("Failed", "err", err) 172 return 173 } 174 175 tx, err := contract.Withdraw() 176 177 if tx != nil { 178 txhash = tx.Hash() 179 log.Info("Note: please wait until the proposal TX gets into a block!", "tx", txhash.Hex()) 180 } 181 182 return 183 } 184 185 //============================================================================= 186 // Generic proposal info 187 //============================================================================= 188 189 type ProposalInfo struct { 190 Proposal common.Address 191 Proposer common.Address 192 CreatedBlock uint64 193 Deadline uint64 194 QuorumWeight *hexutil.Big 195 TotalWeight *hexutil.Big 196 RejectWeight *hexutil.Big 197 AcceptWeight *hexutil.Big 198 Finished bool 199 Accepted bool 200 Balance *hexutil.Big 201 } 202 203 func getBalance(backend Backend, address common.Address) (*hexutil.Big, error) { 204 curr_block := backend.CurrentBlock() 205 206 state, _, err := backend.StateAndHeaderByNumber( 207 nil, rpc.BlockNumber(curr_block.Number().Int64())) 208 if err != nil { 209 log.Error("Failed at state", "err", err) 210 return nil, err 211 } 212 213 return (*hexutil.Big)(state.GetBalance(address)), nil 214 } 215 216 func proposalInfo(backend Backend, address common.Address) (*ProposalInfo, error) { 217 if (address == common.Address{}) { 218 return nil, nil 219 } 220 221 proposal, err := energi_abi.NewIProposalCaller( 222 address, backend.(bind.ContractCaller)) 223 if err != nil { 224 log.Error("Failed at NewIProposalCaller", "err", err) 225 return nil, err 226 } 227 228 call_opts := &bind.CallOpts{ 229 GasLimit: energi_params.UnlimitedGas, 230 } 231 232 proposer, err := proposal.FeePayer(call_opts) 233 if err != nil { 234 log.Error("Failed at FeePayer", "err", err) 235 return nil, err 236 } 237 238 block, err := proposal.CreatedBlock(call_opts) 239 if err != nil { 240 log.Error("Failed at CreatedBlock", "err", err) 241 return nil, err 242 } 243 244 deadline, err := proposal.Deadline(call_opts) 245 if err != nil { 246 log.Error("Failed at Deadline", "err", err) 247 return nil, err 248 } 249 250 quorum_w, err := proposal.QuorumWeight(call_opts) 251 if err != nil { 252 log.Error("Failed at QuorumWeight", "err", err) 253 return nil, err 254 } 255 256 total_w, err := proposal.TotalWeight(call_opts) 257 if err != nil { 258 log.Error("Failed at TotalWeight", "err", err) 259 return nil, err 260 } 261 262 rejected_w, err := proposal.RejectedWeight(call_opts) 263 if err != nil { 264 log.Error("Failed at RejectedWeight", "err", err) 265 return nil, err 266 } 267 268 accepted_w, err := proposal.AcceptedWeight(call_opts) 269 if err != nil { 270 log.Error("Failed at AcceptedWeight", "err", err) 271 return nil, err 272 } 273 274 finished, err := proposal.IsFinished(call_opts) 275 if err != nil { 276 log.Error("Failed at IsFinished", "err", err) 277 return nil, err 278 } 279 280 accepted, err := proposal.IsAccepted(call_opts) 281 if err != nil { 282 log.Error("Failed at IsAccepted", "err", err) 283 return nil, err 284 } 285 286 balance, err := getBalance(backend, address) 287 if err != nil { 288 log.Error("Failed at getBalance", "err", err) 289 return nil, err 290 } 291 292 p := &ProposalInfo{ 293 Proposal: address, 294 Proposer: proposer, 295 CreatedBlock: block.Uint64(), 296 Deadline: deadline.Uint64(), 297 QuorumWeight: (*hexutil.Big)(quorum_w), 298 TotalWeight: (*hexutil.Big)(total_w), 299 RejectWeight: (*hexutil.Big)(rejected_w), 300 AcceptWeight: (*hexutil.Big)(accepted_w), 301 Finished: finished, 302 Accepted: accepted, 303 Balance: balance, 304 } 305 return p, nil 306 } 307 308 //============================================================================= 309 // SC-15: Upgrade API 310 //============================================================================= 311 312 type UpgradeProposalInfo struct { 313 ProposalInfo 314 Impl common.Address 315 Proxy common.Address 316 } 317 318 func (g *GovernanceAPI) upgradeProposalInfo(num *big.Int, proxy common.Address) ([]UpgradeProposalInfo, error) { 319 proxy_obj, err := energi_abi.NewIGovernedProxyCaller( 320 proxy, g.backend.(bind.ContractCaller)) 321 if err != nil { 322 log.Error("Failed NewIGovernedProxyCaller", "err", err) 323 return nil, err 324 } 325 326 call_opts := &bind.CallOpts{ 327 BlockNumber: num, 328 GasLimit: energi_params.UnlimitedGas, 329 } 330 proposals, err := proxy_obj.ListUpgradeProposals(call_opts) 331 if err != nil { 332 log.Error("Failed ListUpgradeProposals", "err", err) 333 return nil, err 334 } 335 336 ret := make([]UpgradeProposalInfo, 0, len(proposals)) 337 for i, p := range proposals { 338 pInfo, err := proposalInfo(g.backend, p) 339 if err != nil { 340 log.Error("Failed at proposalInfo", "err", err) 341 continue 342 } 343 344 ret = append(ret, UpgradeProposalInfo{ProposalInfo: *pInfo}) 345 impl, err := proxy_obj.UpgradeProposalImpl(call_opts, p) 346 if err != nil { 347 log.Error("Failed UpgradeProposalImpl", "err", err) 348 continue 349 } 350 ret[i].Impl = impl 351 ret[i].Proxy = proxy 352 } 353 354 return ret, nil 355 } 356 357 func (g *GovernanceAPI) governedProxy( 358 password *string, 359 owner common.Address, 360 proxy common.Address, 361 ) (session *energi_abi.IGovernedProxySession, err error) { 362 contract, err := energi_abi.NewIGovernedProxy( 363 proxy, g.backend.(bind.ContractBackend)) 364 if err != nil { 365 return nil, err 366 } 367 368 session = &energi_abi.IGovernedProxySession{ 369 Contract: contract, 370 CallOpts: bind.CallOpts{ 371 From: owner, 372 GasLimit: energi_params.UnlimitedGas, 373 }, 374 TransactOpts: bind.TransactOpts{ 375 From: owner, 376 Signer: createSignerCallback(g.backend, password), 377 Value: common.Big0, 378 GasLimit: upgradeCallGas, 379 }, 380 } 381 return 382 } 383 384 type UpgradeProposals struct { 385 Treasury []UpgradeProposalInfo 386 MasternodeRegistry []UpgradeProposalInfo 387 StakerReward []UpgradeProposalInfo 388 BackboneReward []UpgradeProposalInfo 389 SporkRegistry []UpgradeProposalInfo 390 CheckpointRegistry []UpgradeProposalInfo 391 BlacklistRegistry []UpgradeProposalInfo 392 MasternodeToken []UpgradeProposalInfo 393 } 394 395 func (g *GovernanceAPI) UpgradeInfo() *UpgradeProposals { 396 data, err := g.uInfoCache.Get(g.backend, g.upgradeInfo) 397 if err != nil || data == nil { 398 log.Error("UpgradeInfo failed", "err", err) 399 return nil 400 } 401 402 return data.(*UpgradeProposals) 403 } 404 405 func (g *GovernanceAPI) upgradeInfo(num *big.Int) (interface{}, error) { 406 var err error 407 ret := new(UpgradeProposals) 408 ret.Treasury, err = g.upgradeProposalInfo(num, energi_params.Energi_Treasury) 409 if err != nil { 410 log.Error("Treasury info fetch failed", "err", err) 411 } 412 413 ret.MasternodeRegistry, err = g.upgradeProposalInfo(num, energi_params.Energi_MasternodeRegistry) 414 if err != nil { 415 log.Error("MasternodeRegistry info fetch failed", "err", err) 416 } 417 418 ret.StakerReward, err = g.upgradeProposalInfo(num, energi_params.Energi_StakerReward) 419 if err != nil { 420 log.Error("StakerReward info fetch failed", "err", err) 421 } 422 423 ret.BackboneReward, err = g.upgradeProposalInfo(num, energi_params.Energi_BackboneReward) 424 if err != nil { 425 log.Error("BackboneReward info fetch failed", "err", err) 426 } 427 428 ret.SporkRegistry, err = g.upgradeProposalInfo(num, energi_params.Energi_SporkRegistry) 429 if err != nil { 430 log.Error("SporkRegistry info fetch failed", "err", err) 431 } 432 433 ret.CheckpointRegistry, err = g.upgradeProposalInfo(num, energi_params.Energi_CheckpointRegistry) 434 if err != nil { 435 log.Error("CheckpointRegistry info fetch failed", "err", err) 436 } 437 438 ret.BlacklistRegistry, err = g.upgradeProposalInfo(num, energi_params.Energi_BlacklistRegistry) 439 if err != nil { 440 log.Error("BlacklistRegistry info fetch failed", "err", err) 441 } 442 443 ret.MasternodeToken, err = g.upgradeProposalInfo(num, energi_params.Energi_MasternodeToken) 444 if err != nil { 445 log.Error("MasternodeToken info fetch failed", "err", err) 446 } 447 448 return ret, nil 449 } 450 451 func (g *GovernanceAPI) UpgradePropose( 452 proxy common.Address, 453 new_impl common.Address, 454 period uint64, 455 fee *hexutil.Big, 456 payer common.Address, 457 password *string, 458 ) (txhash common.Hash, err error) { 459 session, err := g.governedProxy(password, payer, proxy) 460 if err != nil { 461 log.Error("Failed", "err", err) 462 return 463 } 464 465 session.TransactOpts.Value = fee.ToInt() 466 tx, err := session.ProposeUpgrade(new_impl, new(big.Int).SetUint64(period)) 467 468 if tx != nil { 469 txhash = tx.Hash() 470 log.Info("Note: please wait until the proposal TX gets into a block!", "tx", txhash.Hex()) 471 } 472 473 return 474 } 475 476 func (g *GovernanceAPI) UpgradePerform( 477 proxy common.Address, 478 proposal common.Address, 479 payer common.Address, 480 password *string, 481 ) (txhash common.Hash, err error) { 482 session, err := g.governedProxy(password, payer, proxy) 483 if err != nil { 484 log.Error("Failed", "err", err) 485 return 486 } 487 488 tx, err := session.Upgrade(proposal) 489 490 if tx != nil { 491 txhash = tx.Hash() 492 log.Info("Note: please wait until the upgrade TX gets into a block!", "tx", txhash.Hex()) 493 } 494 495 return 496 } 497 498 func (g *GovernanceAPI) UpgradeCollect( 499 proxy common.Address, 500 proposal common.Address, 501 payer common.Address, 502 password *string, 503 ) (txhash common.Hash, err error) { 504 session, err := g.governedProxy(password, payer, proxy) 505 if err != nil { 506 log.Error("Failed", "err", err) 507 return 508 } 509 510 tx, err := session.CollectUpgradeProposal(proposal) 511 512 if tx != nil { 513 txhash = tx.Hash() 514 log.Info("Note: please wait until the proposal TX gets into a block!", "tx", txhash.Hex()) 515 } 516 517 return 518 } 519 520 //============================================================================= 521 // GOV-9: Treasury API 522 //============================================================================= 523 524 func (g *GovernanceAPI) treasury( 525 password *string, 526 payer common.Address, 527 ) (session *energi_abi.ITreasurySession, err error) { 528 contract, err := energi_abi.NewITreasury( 529 energi_params.Energi_Treasury, g.backend.(bind.ContractBackend)) 530 if err != nil { 531 return nil, err 532 } 533 534 session = &energi_abi.ITreasurySession{ 535 Contract: contract, 536 CallOpts: bind.CallOpts{ 537 From: payer, 538 GasLimit: energi_params.UnlimitedGas, 539 }, 540 TransactOpts: bind.TransactOpts{ 541 From: payer, 542 Signer: createSignerCallback(g.backend, password), 543 Value: common.Big0, 544 GasLimit: proposalCallGas, 545 }, 546 } 547 return 548 } 549 550 type BudgetProposalInfo struct { 551 ProposalInfo 552 ProposedAmount *hexutil.Big 553 PaidAmount *hexutil.Big 554 RefUUID string 555 } 556 557 type BudgetInfo struct { 558 Balance *hexutil.Big 559 Proposals []BudgetProposalInfo 560 } 561 562 func (g *GovernanceAPI) BudgetInfo() (*BudgetInfo, error) { 563 data, err := g.bInfoCache.Get(g.backend, g.budgetInfo) 564 if err != nil || data == nil { 565 log.Error("BudgetInfo failed", "err", err) 566 return nil, err 567 } 568 569 return data.(*BudgetInfo), nil 570 } 571 572 func (g *GovernanceAPI) budgetInfo(num *big.Int) (interface{}, error) { 573 treasury, err := energi_abi.NewITreasuryCaller( 574 energi_params.Energi_Treasury, g.backend.(bind.ContractCaller)) 575 if err != nil { 576 log.Error("Failed NewITreasuryCaller", "err", err) 577 return nil, err 578 } 579 580 proxy, err := energi_abi.NewIGovernedProxyCaller( 581 energi_params.Energi_Treasury, g.backend.(bind.ContractCaller)) 582 if err != nil { 583 log.Error("Failed NewITreasuryCaller", "err", err) 584 return nil, err 585 } 586 587 call_opts := &bind.CallOpts{ 588 BlockNumber: num, 589 GasLimit: energi_params.UnlimitedGas, 590 } 591 592 proposals, err := treasury.ListProposals(call_opts) 593 if err != nil { 594 log.Error("Failed ListProposals", "err", err) 595 return nil, err 596 } 597 598 impl, err := proxy.Impl(call_opts) 599 if err != nil { 600 log.Error("Failed Impl", "err", err) 601 return nil, err 602 } 603 604 ret := make([]BudgetProposalInfo, 0, len(proposals)) 605 for i, p := range proposals { 606 pInfo, err := proposalInfo(g.backend, p) 607 if err != nil { 608 log.Error("Failed at proposalInfo", "err", err) 609 continue 610 } 611 612 ret = append(ret, BudgetProposalInfo{ProposalInfo: *pInfo}) 613 614 budger_proposal, err := energi_abi.NewIBudgetProposalCaller( 615 p, g.backend.(bind.ContractCaller)) 616 if err != nil { 617 log.Error("Failed at NewIBudgetProposalCaller", "err", err) 618 return nil, err 619 } 620 621 proposed_amount, err := budger_proposal.ProposedAmount(call_opts) 622 if err != nil { 623 log.Error("Failed ProposedAmount", "err", err) 624 continue 625 } 626 paid_amount, err := budger_proposal.PaidAmount(call_opts) 627 if err != nil { 628 log.Error("Failed ProposedAmount", "err", err) 629 continue 630 } 631 ref_uuid, err := budger_proposal.RefUuid(call_opts) 632 if err != nil { 633 log.Error("Failed ProposedAmount", "err", err) 634 continue 635 } 636 ret[i].ProposedAmount = (*hexutil.Big)(proposed_amount) 637 ret[i].PaidAmount = (*hexutil.Big)(paid_amount) 638 ret[i].RefUUID = uuid.UUID(common.LeftPadBytes(ref_uuid.Bytes(), 16)).String() 639 } 640 641 balance, err := getBalance(g.backend, impl) 642 if err != nil { 643 log.Error("Failed at getBalance", "err", err) 644 } 645 646 budget := &BudgetInfo{ 647 Balance: balance, 648 Proposals: ret, 649 } 650 651 return budget, nil 652 } 653 654 func (g *GovernanceAPI) BudgetPropose( 655 amount *hexutil.Big, 656 ref_uuid string, 657 period uint64, 658 fee *hexutil.Big, 659 payer common.Address, 660 password *string, 661 ) (txhash common.Hash, err error) { 662 session, err := g.treasury(password, payer) 663 if err != nil { 664 log.Error("Failed", "err", err) 665 return 666 } 667 668 ref_uuid_b := uuid.Parse(ref_uuid) 669 if ref_uuid_b == nil { 670 err = errors.New("Failed to parse UUID") 671 log.Error("Failed", "err", err) 672 return 673 } 674 675 session.TransactOpts.Value = fee.ToInt() 676 tx, err := session.Propose( 677 (*big.Int)(amount), 678 new(big.Int).SetBytes(ref_uuid_b), 679 new(big.Int).SetUint64(period)) 680 681 if tx != nil { 682 txhash = tx.Hash() 683 log.Info("Note: please wait until the proposal TX gets into a block!", "tx", txhash.Hex()) 684 } 685 686 return 687 }