diff options
author | Wei-Ning Huang <w@dexon.org> | 2018-12-18 20:25:58 +0800 |
---|---|---|
committer | Wei-Ning Huang <w@dexon.org> | 2019-04-09 21:32:54 +0800 |
commit | a6fd9c42d0d2632961f1f8adfe9db85ac125d294 (patch) | |
tree | 329aa6351559fc38db674879265ca3102ca74e43 /core/vm | |
parent | 2e36ada5739437b6860d3978ac3e19f4073d94be (diff) | |
download | go-tangerine-a6fd9c42d0d2632961f1f8adfe9db85ac125d294.tar go-tangerine-a6fd9c42d0d2632961f1f8adfe9db85ac125d294.tar.gz go-tangerine-a6fd9c42d0d2632961f1f8adfe9db85ac125d294.tar.bz2 go-tangerine-a6fd9c42d0d2632961f1f8adfe9db85ac125d294.tar.lz go-tangerine-a6fd9c42d0d2632961f1f8adfe9db85ac125d294.tar.xz go-tangerine-a6fd9c42d0d2632961f1f8adfe9db85ac125d294.tar.zst go-tangerine-a6fd9c42d0d2632961f1f8adfe9db85ac125d294.zip |
core: vm: add undelegate fund lockup mechanism (#94)
Only allow a user to withdraw funds after a certain lockup
period. This way, the fund of a bad actor could be confiscated before he
could escape.
Diffstat (limited to 'core/vm')
-rw-r--r-- | core/vm/governance.go | 198 | ||||
-rw-r--r-- | core/vm/governance_test.go | 77 |
2 files changed, 235 insertions, 40 deletions
diff --git a/core/vm/governance.go b/core/vm/governance.go index 8c6f70a04..f423c4ca3 100644 --- a/core/vm/governance.go +++ b/core/vm/governance.go @@ -165,6 +165,10 @@ const GovernanceABIJSON = ` { "name": "url", "type": "string" + }, + { + "name": "unstaked", + "type": "bool" } ], "payable": false, @@ -253,6 +257,10 @@ const GovernanceABIJSON = ` { "name": "value", "type": "uint256" + }, + { + "name": "undelegated_at", + "type": "uint256" } ], "payable": false, @@ -443,6 +451,20 @@ const GovernanceABIJSON = ` }, { "constant": true, + "inputs": [], + "name": "lockupPeriod", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, "inputs": [ { "name": "", @@ -568,6 +590,10 @@ const GovernanceABIJSON = ` "type": "uint256" }, { + "name": "LockupPeriod", + "type": "uint256" + }, + { "name": "BlockReward", "type": "uint256" }, @@ -807,6 +833,20 @@ const GovernanceABIJSON = ` "payable": false, "stateMutability": "nonpayable", "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "NodeAddress", + "type": "address" + } + ], + "name": "withdraw", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" } ] ` @@ -958,6 +998,12 @@ func RunGovernanceContract(evm *EVM, input []byte, contract *Contract) (ret []by return nil, errExecutionReverted } return g.updateConfiguration(&cfg) + case "withdraw": + address := common.Address{} + if err := method.Inputs.Unpack(&address, arguments); err != nil { + return nil, errExecutionReverted + } + return g.withdraw(address) // -------------------------------- // Solidity auto generated methods. @@ -1087,6 +1133,12 @@ func RunGovernanceContract(evm *EVM, input []byte, contract *Contract) (ret []by return nil, errExecutionReverted } return res, nil + case "lockupPeriod": + res, err := method.Outputs.Pack(g.state.LockupPeriod()) + if err != nil { + return nil, errExecutionReverted + } + return res, nil case "minBlockInterval": res, err := method.Outputs.Pack(g.state.MinBlockInterval()) if err != nil { @@ -1180,6 +1232,7 @@ const ( dkgFinalizedsCountLoc ownerLoc minStakeLoc + lockupPeriodLoc blockRewardLoc blockGasLimitLoc numChainsLoc @@ -1360,6 +1413,7 @@ func (s *GovernanceStateHelper) PushRoundHeight(height *big.Int) { // string email; // string location; // string url; +// bool unstaked; // } // // Node[] nodes; @@ -1372,9 +1426,10 @@ type nodeInfo struct { Email string Location string Url string + Unstaked bool } -const nodeStructSize = 7 +const nodeStructSize = 8 func (s *GovernanceStateHelper) LenNodes() *big.Int { return s.getStateBigInt(big.NewInt(nodesLoc)) @@ -1414,6 +1469,10 @@ func (s *GovernanceStateHelper) Node(index *big.Int) *nodeInfo { loc = new(big.Int).Add(elementBaseLoc, big.NewInt(6)) node.Url = string(s.readBytes(loc)) + // Unstaked. + loc = new(big.Int).Add(elementBaseLoc, big.NewInt(7)) + node.Unstaked = s.getStateBigInt(loc).Cmp(big.NewInt(0)) > 0 + return node } func (s *GovernanceStateHelper) PushNode(n *nodeInfo) { @@ -1454,6 +1513,14 @@ func (s *GovernanceStateHelper) UpdateNode(index *big.Int, n *nodeInfo) { // Url. loc = new(big.Int).Add(elementBaseLoc, big.NewInt(6)) s.writeBytes(loc, []byte(n.Url)) + + // Unstaked. + loc = new(big.Int).Add(elementBaseLoc, big.NewInt(7)) + val := big.NewInt(0) + if n.Unstaked { + val = big.NewInt(1) + } + s.setStateBigInt(loc, val) } func (s *GovernanceStateHelper) PopLastNode() { // Decrease length by 1. @@ -1499,14 +1566,16 @@ func (s *GovernanceStateHelper) DeleteNodesOffset(addr common.Address) { // address node; // address owner; // uint256 value; +// uint256 undelegated_at; // } type delegatorInfo struct { - Owner common.Address - Value *big.Int + Owner common.Address + Value *big.Int + UndelegatedAt *big.Int } -const delegatorStructSize = 2 +const delegatorStructSize = 3 // mapping(address => Delegator[]) public delegators; func (s *GovernanceStateHelper) LenDelegators(nodeAddr common.Address) *big.Int { @@ -1528,6 +1597,10 @@ func (s *GovernanceStateHelper) Delegator(nodeAddr common.Address, offset *big.I loc = new(big.Int).Add(elementBaseLoc, big.NewInt(1)) delegator.Value = s.getStateBigInt(loc) + // UndelegatedAt. + loc = new(big.Int).Add(elementBaseLoc, big.NewInt(2)) + delegator.UndelegatedAt = s.getStateBigInt(loc) + return delegator } func (s *GovernanceStateHelper) PushDelegator(nodeAddr common.Address, delegator *delegatorInfo) { @@ -1550,6 +1623,10 @@ func (s *GovernanceStateHelper) UpdateDelegator(nodeAddr common.Address, offset // Value. loc = new(big.Int).Add(elementBaseLoc, big.NewInt(1)) s.setStateBigInt(loc, delegator.Value) + + // UndelegatedAt. + loc = new(big.Int).Add(elementBaseLoc, big.NewInt(2)) + s.setStateBigInt(loc, delegator.UndelegatedAt) } func (s *GovernanceStateHelper) PopLastDelegator(nodeAddr common.Address) { // Decrease length by 1. @@ -1558,7 +1635,10 @@ func (s *GovernanceStateHelper) PopLastDelegator(nodeAddr common.Address) { loc := s.getMapLoc(big.NewInt(delegatorsLoc), nodeAddr.Bytes()) s.setStateBigInt(loc, newArrayLength) - s.UpdateDelegator(nodeAddr, newArrayLength, &delegatorInfo{Value: big.NewInt(0)}) + s.UpdateDelegator(nodeAddr, newArrayLength, &delegatorInfo{ + Value: big.NewInt(0), + UndelegatedAt: big.NewInt(0), + }) } // mapping(address => mapping(address => uint256)) delegatorsOffset; @@ -1676,17 +1756,16 @@ func (s *GovernanceStateHelper) SetOwner(newOwner common.Address) { func (s *GovernanceStateHelper) MinStake() *big.Int { return s.getStateBigInt(big.NewInt(minStakeLoc)) } -func (s *GovernanceStateHelper) SetMinStake(stake *big.Int) { - s.setStateBigInt(big.NewInt(minStakeLoc), stake) + +// uint256 public lockupPeriod; +func (s *GovernanceStateHelper) LockupPeriod() *big.Int { + return s.getStateBigInt(big.NewInt(lockupPeriodLoc)) } // uint256 public blockReward; func (s *GovernanceStateHelper) BlockReward() *big.Int { return s.getStateBigInt(big.NewInt(blockRewardLoc)) } -func (s *GovernanceStateHelper) SetBlockReward(reward *big.Int) { - s.setStateBigInt(big.NewInt(blockRewardLoc), reward) -} // uint256 public blockGasLimit; func (s *GovernanceStateHelper) BlockGasLimit() *big.Int { @@ -1764,6 +1843,7 @@ const phiRatioMultiplier = 1000000.0 func (s *GovernanceStateHelper) Configuration() *params.DexconConfig { return ¶ms.DexconConfig{ MinStake: s.getStateBigInt(big.NewInt(minStakeLoc)), + LockupPeriod: s.getStateBigInt(big.NewInt(lockupPeriodLoc)).Uint64(), BlockReward: s.getStateBigInt(big.NewInt(blockRewardLoc)), BlockGasLimit: s.getStateBigInt(big.NewInt(blockGasLimitLoc)).Uint64(), NumChains: uint32(s.getStateBigInt(big.NewInt(numChainsLoc)).Uint64()), @@ -1781,6 +1861,7 @@ func (s *GovernanceStateHelper) Configuration() *params.DexconConfig { // UpdateConfiguration updates system configuration. func (s *GovernanceStateHelper) UpdateConfiguration(cfg *params.DexconConfig) { s.setStateBigInt(big.NewInt(minStakeLoc), cfg.MinStake) + s.setStateBigInt(big.NewInt(lockupPeriodLoc), big.NewInt(int64(cfg.LockupPeriod))) s.setStateBigInt(big.NewInt(blockRewardLoc), cfg.BlockReward) s.setStateBigInt(big.NewInt(blockGasLimitLoc), big.NewInt(int64(cfg.BlockGasLimit))) s.setStateBigInt(big.NewInt(numChainsLoc), big.NewInt(int64(cfg.NumChains))) @@ -1796,6 +1877,7 @@ func (s *GovernanceStateHelper) UpdateConfiguration(cfg *params.DexconConfig) { type rawConfigStruct struct { MinStake *big.Int + LockupPeriod *big.Int BlockReward *big.Int BlockGasLimit *big.Int NumChains *big.Int @@ -1812,6 +1894,7 @@ type rawConfigStruct struct { // UpdateConfigurationRaw updates system configuration. func (s *GovernanceStateHelper) UpdateConfigurationRaw(cfg *rawConfigStruct) { s.setStateBigInt(big.NewInt(minStakeLoc), cfg.MinStake) + s.setStateBigInt(big.NewInt(lockupPeriodLoc), cfg.LockupPeriod) s.setStateBigInt(big.NewInt(blockRewardLoc), cfg.BlockReward) s.setStateBigInt(big.NewInt(blockGasLimitLoc), cfg.BlockGasLimit) s.setStateBigInt(big.NewInt(numChainsLoc), cfg.NumChains) @@ -2082,8 +2165,9 @@ func (g *GovernanceContract) delegate(nodeAddr common.Address) ([]byte, error) { // Push delegator record. offset = g.state.LenDelegators(nodeAddr) g.state.PushDelegator(nodeAddr, &delegatorInfo{ - Owner: caller, - Value: value, + Owner: caller, + Value: value, + UndelegatedAt: big.NewInt(0), }) g.state.PutDelegatorOffset(nodeAddr, caller, offset) g.state.emitDelegated(nodeAddr, caller, value) @@ -2146,18 +2230,62 @@ func (g *GovernanceContract) stake( return g.useGas(100000) } -func (g *GovernanceContract) undelegateHelper(nodeAddr, owner common.Address) ([]byte, error) { +func (g *GovernanceContract) undelegateHelper(nodeAddr, caller common.Address) ([]byte, error) { nodeOffset := g.state.NodesOffset(nodeAddr) if nodeOffset.Cmp(big.NewInt(0)) < 0 { return nil, errExecutionReverted } - offset := g.state.DelegatorsOffset(nodeAddr, owner) + offset := g.state.DelegatorsOffset(nodeAddr, caller) if offset.Cmp(big.NewInt(0)) < 0 { return nil, errExecutionReverted } delegator := g.state.Delegator(nodeAddr, offset) + + // Set undelegate time. + delegator.UndelegatedAt = g.evm.Time + g.state.UpdateDelegator(nodeAddr, offset, delegator) + + // Subtract from the total staked of node. + node := g.state.Node(nodeOffset) + node.Staked = new(big.Int).Sub(node.Staked, delegator.Value) + g.state.UpdateNode(nodeOffset, node) + + g.state.emitUndelegated(nodeAddr, caller) + + return g.useGas(100000) +} + +func (g *GovernanceContract) undelegate(nodeAddr common.Address) ([]byte, error) { + return g.undelegateHelper(nodeAddr, g.contract.Caller()) +} + +func (g *GovernanceContract) withdraw(nodeAddr common.Address) ([]byte, error) { + caller := g.contract.Caller() + + nodeOffset := g.state.NodesOffset(nodeAddr) + if nodeOffset.Cmp(big.NewInt(0)) < 0 { + return nil, errExecutionReverted + } + + offset := g.state.DelegatorsOffset(nodeAddr, caller) + if offset.Cmp(big.NewInt(0)) < 0 { + return nil, errExecutionReverted + } + + delegator := g.state.Delegator(nodeAddr, offset) + + // Not yet undelegated. + if delegator.UndelegatedAt.Cmp(big.NewInt(0)) == 0 { + return g.penalize() + } + + unlockTime := new(big.Int).Add(delegator.UndelegatedAt, g.state.LockupPeriod()) + if g.evm.Time.Cmp(unlockTime) <= 0 { + return g.penalize() + } + length := g.state.LenDelegators(nodeAddr) lastIndex := new(big.Int).Sub(length, big.NewInt(1)) @@ -2167,25 +2295,30 @@ func (g *GovernanceContract) undelegateHelper(nodeAddr, owner common.Address) ([ g.state.UpdateDelegator(nodeAddr, offset, lastNode) g.state.PutDelegatorOffset(nodeAddr, lastNode.Owner, offset) } - g.state.DeleteDelegatorsOffset(nodeAddr, owner) + g.state.DeleteDelegatorsOffset(nodeAddr, caller) g.state.PopLastDelegator(nodeAddr) - // Subtract from the total staked of node. - node := g.state.Node(nodeOffset) - node.Staked = new(big.Int).Sub(node.Staked, delegator.Value) - g.state.UpdateNode(nodeOffset, node) - // Return the staked fund. if !g.transfer(GovernanceContractAddress, delegator.Owner, delegator.Value) { return nil, errExecutionReverted } - g.state.emitUndelegated(nodeAddr, owner) - return g.useGas(100000) -} + // We are the last delegator to withdraw the fund, remove the node info. + if g.state.LenDelegators(nodeAddr).Cmp(big.NewInt(0)) == 0 { + length := g.state.LenNodes() + lastIndex := new(big.Int).Sub(length, big.NewInt(1)) -func (g *GovernanceContract) undelegate(nodeAddr common.Address) ([]byte, error) { - return g.undelegateHelper(nodeAddr, g.contract.Caller()) + // Delete the node. + if offset.Cmp(lastIndex) != 0 { + lastNode := g.state.Node(lastIndex) + g.state.UpdateNode(offset, lastNode) + g.state.PutNodesOffset(lastNode.Owner, offset) + } + g.state.DeleteNodesOffset(nodeAddr) + g.state.PopLastNode() + } + + return g.useGas(100000) } func (g *GovernanceContract) unstake() ([]byte, error) { @@ -2206,17 +2339,10 @@ func (g *GovernanceContract) unstake() ([]byte, error) { i = i.Sub(i, big.NewInt(1)) } - length := g.state.LenNodes() - lastIndex := new(big.Int).Sub(length, big.NewInt(1)) - - // Delete the node. - if offset.Cmp(lastIndex) != 0 { - lastNode := g.state.Node(lastIndex) - g.state.UpdateNode(offset, lastNode) - g.state.PutNodesOffset(lastNode.Owner, offset) - } - g.state.DeleteNodesOffset(caller) - g.state.PopLastNode() + // Mark node as unstaked. + node := g.state.Node(offset) + node.Unstaked = true + g.state.UpdateNode(offset, node) g.state.emitUnstaked(caller) diff --git a/core/vm/governance_test.go b/core/vm/governance_test.go index 7718e99d2..018306992 100644 --- a/core/vm/governance_test.go +++ b/core/vm/governance_test.go @@ -103,6 +103,8 @@ func (g *GovernanceContractTestSuite) SetupTest() { g.s = &GovernanceStateHelper{stateDB} config := params.TestnetChainConfig.Dexcon + config.LockupPeriod = 1000 + g.config = config // Give governance contract balance so it will not be deleted because of being an empty state object. @@ -155,7 +157,7 @@ func (g *GovernanceContractTestSuite) call(caller common.Address, input []byte, } return 0, false }, - Time: big.NewInt(time.Now().UnixNano() / 1000000000), + Time: big.NewInt(time.Now().UnixNano() / 1000000), BlockNumber: big.NewInt(0), } @@ -180,7 +182,7 @@ func (g *GovernanceContractTestSuite) TestTransferOwnership() { g.Require().Equal(addr, g.s.Owner()) } -func (g *GovernanceContractTestSuite) TestStakeUnstakeWithoutDelegators() { +func (g *GovernanceContractTestSuite) TestStakeUnstakeWithoutExtraDelegators() { privKey, addr := g.newPrefundAccount() pk := crypto.FromECDSAPub(&privKey.PublicKey) @@ -210,6 +212,17 @@ func (g *GovernanceContractTestSuite) TestStakeUnstakeWithoutDelegators() { g.Require().Nil(err) _, err = g.call(addr, input, big.NewInt(0)) g.Require().Nil(err) + g.Require().Equal(1, int(g.s.LenDelegators(addr).Uint64())) + g.Require().Equal(1, int(g.s.LenNodes().Uint64())) + + // Wait for lockup time than withdraw. + time.Sleep(time.Second * 2) + input, err = abiObject.Pack("withdraw", addr) + g.Require().Nil(err) + _, err = g.call(addr, input, big.NewInt(0)) + g.Require().Nil(err) + + g.Require().Equal(0, int(g.s.LenDelegators(addr).Uint64())) g.Require().Equal(0, int(g.s.LenNodes().Uint64())) // Stake 2 nodes, and unstake the first then the second. @@ -235,6 +248,12 @@ func (g *GovernanceContractTestSuite) TestStakeUnstakeWithoutDelegators() { g.Require().Nil(err) _, err = g.call(addr2, input, big.NewInt(0)) g.Require().Nil(err) + time.Sleep(time.Second * 2) + input, err = abiObject.Pack("withdraw", addr2) + g.Require().Nil(err) + _, err = g.call(addr2, input, big.NewInt(0)) + g.Require().Nil(err) + g.Require().Equal(1, int(g.s.LenNodes().Uint64())) g.Require().Equal("Test1", g.s.Node(big.NewInt(0)).Name) g.Require().Equal(-1, int(g.s.NodesOffset(addr2).Int64())) @@ -244,6 +263,12 @@ func (g *GovernanceContractTestSuite) TestStakeUnstakeWithoutDelegators() { g.Require().Nil(err) _, err = g.call(addr, input, big.NewInt(0)) g.Require().Nil(err) + time.Sleep(time.Second * 2) + input, err = abiObject.Pack("withdraw", addr) + g.Require().Nil(err) + _, err = g.call(addr, input, big.NewInt(0)) + g.Require().Nil(err) + g.Require().Equal(0, int(g.s.LenNodes().Uint64())) g.Require().Equal(-1, int(g.s.NodesOffset(addr).Int64())) g.Require().Equal(-1, int(g.s.DelegatorsOffset(addr, addr).Int64())) @@ -309,16 +334,49 @@ func (g *GovernanceContractTestSuite) TestDelegateUndelegate() { g.Require().Nil(err) _, err = g.call(addrDelegator, input, big.NewInt(0)) g.Require().Nil(err) + + // Withdraw within lockup time should fail. + input, err = abiObject.Pack("withdraw", addr) + g.Require().Nil(err) + _, err = g.call(addrDelegator, input, big.NewInt(0)) + g.Require().NotNil(err) + + g.Require().Equal(3, int(g.s.LenDelegators(addr).Uint64())) + g.Require().Equal(balanceBeforeUnDelegate, g.stateDB.GetBalance(addrDelegator)) + g.Require().NotEqual(-1, int(g.s.DelegatorsOffset(addr, addrDelegator).Int64())) + + // Wait for lockup time than withdraw. + time.Sleep(time.Second * 2) + input, err = abiObject.Pack("withdraw", addr) + g.Require().Nil(err) + _, err = g.call(addrDelegator, input, big.NewInt(0)) + g.Require().Nil(err) + g.Require().Equal(2, int(g.s.LenDelegators(addr).Uint64())) g.Require().Equal(new(big.Int).Add(balanceBeforeUnDelegate, amount), g.stateDB.GetBalance(addrDelegator)) g.Require().Equal(-1, int(g.s.DelegatorsOffset(addr, addrDelegator).Int64())) + // Withdraw when their is no delegation should fail. + time.Sleep(time.Second) + input, err = abiObject.Pack("withdraw", addr) + g.Require().Nil(err) + _, err = g.call(addrDelegator, input, big.NewInt(0)) + g.Require().NotNil(err) + // Undelegate addrDelegator2. balanceBeforeUnDelegate = g.stateDB.GetBalance(addrDelegator2) input, err = abiObject.Pack("undelegate", addr) g.Require().Nil(err) _, err = g.call(addrDelegator2, input, big.NewInt(0)) g.Require().Nil(err) + + // Wait for lockup time than withdraw. + time.Sleep(time.Second * 2) + input, err = abiObject.Pack("withdraw", addr) + g.Require().Nil(err) + _, err = g.call(addrDelegator2, input, big.NewInt(0)) + g.Require().Nil(err) + g.Require().Equal(1, int(g.s.LenDelegators(addr).Uint64())) g.Require().Equal(new(big.Int).Add(balanceBeforeUnDelegate, amount), g.stateDB.GetBalance(addrDelegator2)) g.Require().Equal(-1, int(g.s.DelegatorsOffset(addr, addrDelegator2).Int64())) @@ -327,7 +385,7 @@ func (g *GovernanceContractTestSuite) TestDelegateUndelegate() { g.Require().Equal(0, len(g.s.QualifiedNodes())) } -func (g *GovernanceContractTestSuite) TestUnstakeWithDelegators() { +func (g *GovernanceContractTestSuite) TestUnstakeWithExtraDelegators() { privKey, addr := g.newPrefundAccount() pk := crypto.FromECDSAPub(&privKey.PublicKey) @@ -372,6 +430,17 @@ func (g *GovernanceContractTestSuite) TestUnstakeWithDelegators() { g.Require().Nil(err) _, err = g.call(addr, input, big.NewInt(0)) g.Require().Nil(err) + + time.Sleep(time.Second * 2) + input, err = abiObject.Pack("withdraw", addr) + g.Require().Nil(err) + _, err = g.call(addr, input, big.NewInt(0)) + g.Require().Nil(err) + _, err = g.call(addrDelegator, input, big.NewInt(0)) + g.Require().Nil(err) + _, err = g.call(addrDelegator2, input, big.NewInt(0)) + g.Require().Nil(err) + g.Require().Equal(0, int(g.s.LenDelegators(addr).Uint64())) g.Require().Equal(0, int(g.s.LenNodes().Uint64())) @@ -386,7 +455,7 @@ func (g *GovernanceContractTestSuite) TestUpdateConfiguration() { _, addr := g.newPrefundAccount() input, err := abiObject.Pack("updateConfiguration", - new(big.Int).Mul(big.NewInt(1e18), big.NewInt(1e5)), + new(big.Int).Mul(big.NewInt(1e18), big.NewInt(1e5)), big.NewInt(1000), big.NewInt(1e18), big.NewInt(8000000), big.NewInt(6), big.NewInt(250), big.NewInt(2500), big.NewInt(0), big.NewInt(667000), big.NewInt(4), big.NewInt(4), big.NewInt(600000), big.NewInt(900)) g.Require().Nil(err) |