diff options
author | Wei-Ning Huang <w@dexon.org> | 2018-12-18 20:25:58 +0800 |
---|---|---|
committer | Wei-Ning Huang <w@dexon.org> | 2019-03-12 12:19:09 +0800 |
commit | b91ee4d6708dd30098268d3fed7761472a77f881 (patch) | |
tree | da80c3f3e3de4848b528c8406942c842b07d1921 | |
parent | e6db3ea40e521602348b499958a3ff30a5253f8c (diff) | |
download | dexon-b91ee4d6708dd30098268d3fed7761472a77f881.tar.gz dexon-b91ee4d6708dd30098268d3fed7761472a77f881.tar.zst dexon-b91ee4d6708dd30098268d3fed7761472a77f881.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.
-rw-r--r-- | cmd/gdex/dao_test.go | 2 | ||||
-rw-r--r-- | core/genesis_alloc.go | 2 | ||||
-rw-r--r-- | core/vm/governance.go | 198 | ||||
-rw-r--r-- | core/vm/governance_test.go | 77 | ||||
-rw-r--r-- | params/config.go | 11 | ||||
-rw-r--r-- | params/gen_dexcon_config.go | 6 |
6 files changed, 251 insertions, 45 deletions
diff --git a/cmd/gdex/dao_test.go b/cmd/gdex/dao_test.go index 26572762c..22f82d4f4 100644 --- a/cmd/gdex/dao_test.go +++ b/cmd/gdex/dao_test.go @@ -127,7 +127,7 @@ func testDAOForkBlockNewChain(t *testing.T, test int, genesis string, expectBloc } defer db.Close() - genesisHash := common.HexToHash("0xc8e4d0c33d92b7751fe3747f778aa27600508c8c922be1dbbc7db6ee967f4e6c") + genesisHash := common.HexToHash("0x5fc1fdb2eca492d256600c0d96a2ca7bdfd9412ac8557bcab54e05332260e26b") if genesis != "" { genesisHash = daoGenesisHash } diff --git a/core/genesis_alloc.go b/core/genesis_alloc.go index 289c5b09f..ab8032f32 100644 --- a/core/genesis_alloc.go +++ b/core/genesis_alloc.go @@ -21,6 +21,6 @@ package core // Use mkalloc.go to create/update them. // nolint: misspell -const mainnetAllocData = "\xf9\x03\x90\xea\x94\x12E\xa8g/\xa8\x81\u03c5\x8e\xf0\x1d4\xc4+U\u0672c\xff\u050bR\xb7\xd2\xdc\xc8\f\xd2\xe4\x00\x00\x00\x80\x80\x80\u0100\x80\x80\x80\xea\x94*\x9df\x9eG\x91\x84^\xed\x01\xd4\xc0\xff\xf3\xb9'\u0314\xa8\x84\u050bR\xb7\xd2\xdc\xc8\f\xd2\xe4\x00\x00\x00\x80\x80\x80\u0100\x80\x80\x80\xf8\xb7\x94U8QR\xef\xc1\x1bN\xb8\x11\xe9\xdf\x0ex\xf4\u00ff\xa9\x19F\xf8\xa0\x8a\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\x8ai\xe1\r\xe7fv\u0400\x00\x00\x80\xb8A\x04&:l4\x10\xe7\xab\nS\x13\xf6\xca\a[\xb4y\x9d<\x8cr8\x8b\xe9\x8d\xf6\x03\xa1\xe0\x9c%\x12\x96\x05\xbe0\x11x({\xa3\xea\xd7S\xc1\xea\x98&\xc2\xf4\x96\x83U\xa6\xb9{G^\x80\xbb%w\xbeR\x96\xf8D\x91DEXON Test Node 3\x90dexon3@dexon.org\x8eTaipei, Taiwan\x91https://dexon.org\xf8\xb7\x94^.\xa9\x87*J\xd9\x1co\rF\xd6\xed\xd4jL\x96\xdbuS\xf8\xa0\x8a\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\x8ai\xe1\r\xe7fv\u0400\x00\x00\x80\xb8A\x04B+T\"\u007f\x85\x98\x06</\xa4O\x8f_\xa1\x8cg\x10\xe2\xc3\xc0'S\xf7lSKH\x82\xb7\xf2\xf6\t\u070c\xb0:\u052eR\x8c~\x1e\x05\r\xb6L\x96.\x89W'\xf1\xf4B\u06dae\x10\x05\xfa\x97\f^\xf8D\x91DEXON Test Node 2\x90dexon2@dexon.org\x8eTaipei, Taiwan\x91https://dexon.org\xea\x94_\xb3\x95/4\xb5|e/\x9d\u06f4\rh\x8dWlT\x1b\xdc\u050bR\xb7\xd2\xdc\xc8\f\xd2\xe4\x00\x00\x00\x80\x80\x80\u0100\x80\x80\x80\xf8\xb7\x94g\x8eyf\x17KT\x16\xa3\xf9\x18\x8a\x83\xe8\xec\xce%\xf9\x80s\xf8\xa0\x8a\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\x8ai\xe1\r\xe7fv\u0400\x00\x00\x80\xb8A\x04\x88\xfe\xba\u007f\vui|\xe3oeg\xb3\x946p\x9b\x91C1\x956\x9bP\xdc\u007fB\x8c\xa3\x19\x14\x9c\xb0)\xf5\xd5\x05\xbe\v\xb9\x8bD\x96|\xa1\aZ\xb1\xae\u03a2I_\xc5q\xb3-\xe5r9\x9c\x0e\xbd\x8a\xf8D\x91DEXON Test Node 0\x90dexon0@dexon.org\x8eTaipei, Taiwan\x91https://dexon.org\xf8\xb7\x94\xb6-\x1d\"\r\x18PrO\xaa\xe9%\xb6\x9f\u007fB(S\x1eJ\xf8\xa0\x8a\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\x8ai\xe1\r\xe7fv\u0400\x00\x00\x80\xb8A\x04J\x85\xe2\xf8\xdb\x1a\x011\xa3\t\xe7F\u057cR\v\x95\x9d\xe3\xac\xf5ZU\xc7\xd7\x17\xfa4\x91\x00c\x00\x06\xee\x85\xf7\x1a\x9c\xbc\xc08hJ\xfb\xbdNi\xf0\xc5\t\x149\xa98{\u007f\xdb\v\x8cN\xabE\xae\xfc\xf8D\x91DEXON Test Node 1\x90dexon1@dexon.org\x8eTaipei, Taiwan\x91https://dexon.org\xea\x94\xe0\xf8Y4\x03S\x85F\x93\xf57\xea3q\x06\xa3>\x9f\xea\xb0\u050bR\xb7\xd2\xdc\xc8\f\xd2\xe4\x00\x00\x00\x80\x80\x80\u0100\x80\x80\x80" +const mainnetAllocData = "\xf9\x03:\xf8\xb7\x94U8QR\xef\xc1\x1bN\xb8\x11\xe9\xdf\x0ex\xf4\u00ff\xa9\x19F\xf8\xa0\x8a\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\x8ai\xe1\r\xe7fv\u0400\x00\x00\x80\xb8A\x04&:l4\x10\xe7\xab\nS\x13\xf6\xca\a[\xb4y\x9d<\x8cr8\x8b\xe9\x8d\xf6\x03\xa1\xe0\x9c%\x12\x96\x05\xbe0\x11x({\xa3\xea\xd7S\xc1\xea\x98&\xc2\xf4\x96\x83U\xa6\xb9{G^\x80\xbb%w\xbeR\x96\xf8D\x91DEXON Test Node 3\x90dexon3@dexon.org\x8eTaipei, Taiwan\x91https://dexon.org\xf8\xb7\x94^.\xa9\x87*J\xd9\x1co\rF\xd6\xed\xd4jL\x96\xdbuS\xf8\xa0\x8a\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\x8ai\xe1\r\xe7fv\u0400\x00\x00\x80\xb8A\x04B+T\"\u007f\x85\x98\x06</\xa4O\x8f_\xa1\x8cg\x10\xe2\xc3\xc0'S\xf7lSKH\x82\xb7\xf2\xf6\t\u070c\xb0:\u052eR\x8c~\x1e\x05\r\xb6L\x96.\x89W'\xf1\xf4B\u06dae\x10\x05\xfa\x97\f^\xf8D\x91DEXON Test Node 2\x90dexon2@dexon.org\x8eTaipei, Taiwan\x91https://dexon.org\xf8\xb7\x94g\x8eyf\x17KT\x16\xa3\xf9\x18\x8a\x83\xe8\xec\xce%\xf9\x80s\xf8\xa0\x8a\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\x8ai\xe1\r\xe7fv\u0400\x00\x00\x80\xb8A\x04\x88\xfe\xba\u007f\vui|\xe3oeg\xb3\x946p\x9b\x91C1\x956\x9bP\xdc\u007fB\x8c\xa3\x19\x14\x9c\xb0)\xf5\xd5\x05\xbe\v\xb9\x8bD\x96|\xa1\aZ\xb1\xae\u03a2I_\xc5q\xb3-\xe5r9\x9c\x0e\xbd\x8a\xf8D\x91DEXON Test Node 0\x90dexon0@dexon.org\x8eTaipei, Taiwan\x91https://dexon.org\xf8\xb7\x94\xb6-\x1d\"\r\x18PrO\xaa\xe9%\xb6\x9f\u007fB(S\x1eJ\xf8\xa0\x8a\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\x8ai\xe1\r\xe7fv\u0400\x00\x00\x80\xb8A\x04J\x85\xe2\xf8\xdb\x1a\x011\xa3\t\xe7F\u057cR\v\x95\x9d\xe3\xac\xf5ZU\xc7\xd7\x17\xfa4\x91\x00c\x00\x06\xee\x85\xf7\x1a\x9c\xbc\xc08hJ\xfb\xbdNi\xf0\xc5\t\x149\xa98{\u007f\xdb\v\x8cN\xabE\xae\xfc\xf8D\x91DEXON Test Node 1\x90dexon1@dexon.org\x8eTaipei, Taiwan\x91https://dexon.org\ua53f\x8cH\xa6 \xba\xccF\x90\u007f\x9b\x89s-%\xe4z-|\xf7\u050bR\xb7\xd2\xdc\xc8\f\xd2\xe4\x00\x00\x00\x80\x80\x80\u0100\x80\x80\x80\xea\x94\xe0\xf8Y4\x03S\x85F\x93\xf57\xea3q\x06\xa3>\x9f\xea\xb0\u050bR\xb7\xd2\xdc\xc8\f\xd2\xe4\x00\x00\x00\x80\x80\x80\u0100\x80\x80\x80" const testnetAllocData = mainnetAllocData 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) diff --git a/params/config.go b/params/config.go index 24a490c7d..85c13c5af 100644 --- a/params/config.go +++ b/params/config.go @@ -26,8 +26,8 @@ import ( // Genesis hashes to enforce below configs on. var ( - MainnetGenesisHash = common.HexToHash("0xc8e4d0c33d92b7751fe3747f778aa27600508c8c922be1dbbc7db6ee967f4e6c") - TestnetGenesisHash = common.HexToHash("0x63b758fa30bf833430171514448288d4e67c1d6a989d1474fdd5c5888dfe77fd") + MainnetGenesisHash = common.HexToHash("0x5fc1fdb2eca492d256600c0d96a2ca7bdfd9412ac8557bcab54e05332260e26b") + TestnetGenesisHash = common.HexToHash("0x252c41c125e4a9137a39a20b810ddcd33a8023c407cac863ad2326a521375d0f") ) var ( @@ -48,6 +48,7 @@ var ( GenesisCRSText: "In DEXON, we trust.", Owner: common.HexToAddress("BF8C48A620bacc46907f9B89732D25E47A2D7Cf7"), MinStake: new(big.Int).Mul(big.NewInt(1e18), big.NewInt(1e5)), + LockupPeriod: 86400 * 3 * 1000, BlockReward: big.NewInt(1e18), BlockGasLimit: 40000000, NumChains: 4, @@ -87,6 +88,7 @@ var ( GenesisCRSText: "In DEXON, we trust.", Owner: common.HexToAddress("BF8C48A620bacc46907f9B89732D25E47A2D7Cf7"), MinStake: new(big.Int).Mul(big.NewInt(1e18), big.NewInt(1e5)), + LockupPeriod: 86400 * 3 * 1000, BlockReward: big.NewInt(1e18), BlockGasLimit: 40000000, NumChains: 6, @@ -116,6 +118,7 @@ var ( GenesisCRSText: "In DEXON, we trust.", Owner: common.HexToAddress("BF8C48A620bacc46907f9B89732D25E47A2D7Cf7"), MinStake: new(big.Int).Mul(big.NewInt(1e18), big.NewInt(1e5)), + LockupPeriod: 86400 * 3 * 1000, BlockReward: big.NewInt(1e18), BlockGasLimit: 40000000, NumChains: 6, @@ -258,6 +261,7 @@ type DexconConfig struct { GenesisCRSText string `json:"genesisCRSText"` Owner common.Address `json:"owner"` MinStake *big.Int `json:"minStake"` + LockupPeriod uint64 `json:"lockupPeriod"` BlockReward *big.Int `json:"blockReward"` BlockGasLimit uint64 `json:"blockGasLimit"` NumChains uint32 `json:"numChains"` @@ -278,10 +282,11 @@ type dexconConfigSpecMarshaling struct { // String implements the stringer interface, returning the consensus engine details. func (d *DexconConfig) String() string { - return fmt.Sprintf("{GenesisCRSText: %v Owner: %v MinStake: %v BlockReward: %v BlockGasLimit: %v NumChains: %v LambdaBA: %v LambdaDKG: %v K: %v PhiRatio: %v NotarySetSize: %v DKGSetSize: %v RoundInterval: %v MinBlockInterval: %v}", + return fmt.Sprintf("{GenesisCRSText: %v Owner: %v MinStake: %v LockupPeriod: %v BlockReward: %v BlockGasLimit: %v NumChains: %v LambdaBA: %v LambdaDKG: %v K: %v PhiRatio: %v NotarySetSize: %v DKGSetSize: %v RoundInterval: %v MinBlockInterval: %v}", d.GenesisCRSText, d.Owner, d.MinStake, + d.LockupPeriod, d.BlockReward, d.BlockGasLimit, d.NumChains, diff --git a/params/gen_dexcon_config.go b/params/gen_dexcon_config.go index 9cd9395cc..55d98ba37 100644 --- a/params/gen_dexcon_config.go +++ b/params/gen_dexcon_config.go @@ -18,6 +18,7 @@ func (d DexconConfig) MarshalJSON() ([]byte, error) { GenesisCRSText string `json:"genesisCRSText"` Owner common.Address `json:"owner"` MinStake *math.HexOrDecimal256 `json:"minStake"` + LockupPeriod uint64 `json:"lockupPeriod"` BlockReward *math.HexOrDecimal256 `json:"blockReward"` BlockGasLimit uint64 `json:"blockGasLimit"` NumChains uint32 `json:"numChains"` @@ -34,6 +35,7 @@ func (d DexconConfig) MarshalJSON() ([]byte, error) { enc.GenesisCRSText = d.GenesisCRSText enc.Owner = d.Owner enc.MinStake = (*math.HexOrDecimal256)(d.MinStake) + enc.LockupPeriod = d.LockupPeriod enc.BlockReward = (*math.HexOrDecimal256)(d.BlockReward) enc.BlockGasLimit = d.BlockGasLimit enc.NumChains = d.NumChains @@ -54,6 +56,7 @@ func (d *DexconConfig) UnmarshalJSON(input []byte) error { GenesisCRSText *string `json:"genesisCRSText"` Owner *common.Address `json:"owner"` MinStake *math.HexOrDecimal256 `json:"minStake"` + LockupPeriod *uint64 `json:"lockupPeriod"` BlockReward *math.HexOrDecimal256 `json:"blockReward"` BlockGasLimit *uint64 `json:"blockGasLimit"` NumChains *uint32 `json:"numChains"` @@ -79,6 +82,9 @@ func (d *DexconConfig) UnmarshalJSON(input []byte) error { if dec.MinStake != nil { d.MinStake = (*big.Int)(dec.MinStake) } + if dec.LockupPeriod != nil { + d.LockupPeriod = *dec.LockupPeriod + } if dec.BlockReward != nil { d.BlockReward = (*big.Int)(dec.BlockReward) } |