diff options
author | Wei-Ning Huang <w@dexon.org> | 2018-11-19 14:36:36 +0800 |
---|---|---|
committer | Wei-Ning Huang <w@dexon.org> | 2019-03-12 12:19:09 +0800 |
commit | 9181d784fb453423c1a7dafe7c182feafd437376 (patch) | |
tree | 889f51b8751b272c8f6a39a4d9e6d34cf671c4b9 /core | |
parent | be1f06a3acfc85f90a2a9f6dec3235b5bf1f0099 (diff) | |
download | dexon-9181d784fb453423c1a7dafe7c182feafd437376.tar.gz dexon-9181d784fb453423c1a7dafe7c182feafd437376.tar.zst dexon-9181d784fb453423c1a7dafe7c182feafd437376.zip |
governance: implement delegate/undelegate function and add tests (#33)
Implement delegate/undelegate function to allow others to delegate it's
fund to stake on a node. Also added governance contract tests.
Diffstat (limited to 'core')
-rw-r--r-- | core/blockchain.go | 7 | ||||
-rw-r--r-- | core/genesis.go | 4 | ||||
-rw-r--r-- | core/vm/governance.go | 695 | ||||
-rw-r--r-- | core/vm/governance_test.go | 557 |
4 files changed, 1152 insertions, 111 deletions
diff --git a/core/blockchain.go b/core/blockchain.go index 09b08aa9f..a0fa146f7 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1714,7 +1714,7 @@ func (bc *BlockChain) GetPendingHeight() uint64 { return bc.lastPendingHeight } -func (bc *BlockChain) GetPendingBlock() *types.Block { +func (bc *BlockChain) PendingBlock() *types.Block { bc.pendingBlockMu.RLock() defer bc.pendingBlockMu.RUnlock() @@ -1729,7 +1729,10 @@ func (bc *BlockChain) GetPendingBlockByNumber(number uint64) *types.Block { } func (bc *BlockChain) GetPending() (*types.Block, *state.StateDB) { - block := bc.GetPendingBlock() + block := bc.PendingBlock() + if block == nil { + block = bc.CurrentBlock() + } s, err := state.New(block.Header().Root, bc.stateCache) if err != nil { panic(err) diff --git a/core/genesis.go b/core/genesis.go index 440ff1a64..e7d26d4c2 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -303,8 +303,8 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block { } // Genesis CRS. - crs := crypto.Keccak256([]byte(g.Config.Dexcon.GenesisCRSText)) - govStateHelper.PushCRS(common.BytesToHash(crs)) + crs := crypto.Keccak256Hash([]byte(g.Config.Dexcon.GenesisCRSText)) + govStateHelper.PushCRS(crs) // Round 0 height. govStateHelper.PushRoundHeight(big.NewInt(0)) diff --git a/core/vm/governance.go b/core/vm/governance.go index b6a5038c5..36b37b53c 100644 --- a/core/vm/governance.go +++ b/core/vm/governance.go @@ -37,12 +37,34 @@ import ( ) var GovernanceContractAddress = common.HexToAddress("5765692d4e696e6720536f6e696320426f6a6965") -var minStake = big.NewInt(10000000000000) const GovernanceABIJSON = ` [ { "constant": true, + "inputs": [ + { + "name": "", + "type": "address" + }, + { + "name": "", + "type": "address" + } + ], + "name": "delegatorsOffset", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, "inputs": [], "name": "blockReward", "outputs": [ @@ -216,12 +238,20 @@ const GovernanceABIJSON = ` { "name": "", "type": "address" + }, + { + "name": "", + "type": "uint256" } ], - "name": "offset", + "name": "delegators", "outputs": [ { - "name": "", + "name": "owner", + "type": "address" + }, + { + "name": "value", "type": "uint256" } ], @@ -273,6 +303,25 @@ const GovernanceABIJSON = ` }, { "constant": true, + "inputs": [ + { + "name": "", + "type": "address" + } + ], + "name": "nodesOffset", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, "inputs": [], "name": "lambdaDKG", "outputs": [ @@ -422,12 +471,12 @@ const GovernanceABIJSON = ` "inputs": [ { "indexed": true, - "name": "round", + "name": "Round", "type": "uint256" }, { "indexed": false, - "name": "crs", + "name": "CRS", "type": "bytes32" } ], @@ -435,6 +484,69 @@ const GovernanceABIJSON = ` "type": "event" }, { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "NodeAddress", + "type": "address" + } + ], + "name": "Staked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "NodeAddress", + "type": "address" + } + ], + "name": "Unstaked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "NodeAddress", + "type": "address" + }, + { + "indexed": true, + "name": "DelegatorAddress", + "type": "address" + }, + { + "indexed": false, + "name": "Amount", + "type": "uint256" + } + ], + "name": "Delegated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "NodeAddress", + "type": "address" + }, + { + "indexed": true, + "name": "DelegatorAddress", + "type": "address" + } + ], + "name": "Undelegated", + "type": "event" + }, + { "constant": false, "inputs": [ { @@ -507,6 +619,39 @@ const GovernanceABIJSON = ` "type": "function" }, { + "constant": true, + "inputs": [], + "name": "nodesLength", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "NodeAddress", + "type": "address" + } + ], + "name": "delegatorsLength", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { "constant": false, "inputs": [ { @@ -636,17 +781,31 @@ const GovernanceABIJSON = ` "type": "function" }, { - "constant": true, - "inputs": [], - "name": "nodesLength", - "outputs": [ + "constant": false, + "inputs": [ { - "name": "", - "type": "uint256" + "name": "NodeAddress", + "type": "address" } ], + "name": "delegate", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "NodeAddress", + "type": "address" + } + ], + "name": "undelegate", + "outputs": [], "payable": false, - "stateMutability": "view", + "stateMutability": "nonpayable", "type": "function" } ] @@ -658,8 +817,10 @@ var sig2Method map[string]abi.Method var events map[string]abi.Event func init() { + var err error + // Parse governance contract ABI. - abiObject, err := abi.JSON(strings.NewReader(GovernanceABIJSON)) + abiObject, err = abi.JSON(strings.NewReader(GovernanceABIJSON)) if err != nil { panic(err) } @@ -682,8 +843,7 @@ func init() { } // RunGovernanceContract executes governance contract. -func RunGovernanceContract(evm *EVM, input []byte, contract *Contract) ( - ret []byte, err error) { +func RunGovernanceContract(evm *EVM, input []byte, contract *Contract) (ret []byte, err error) { if len(input) < 4 { return nil, nil } @@ -726,8 +886,24 @@ func RunGovernanceContract(evm *EVM, input []byte, contract *Contract) ( return nil, errExecutionReverted } return g.addDKGFinalize(args.Round, args.Finalize) + case "delegate": + address := common.Address{} + if err := method.Inputs.Unpack(&address, arguments); err != nil { + return nil, errExecutionReverted + } + return g.delegate(address) + case "delegatorsLength": + address := common.Address{} + if err := method.Inputs.Unpack(&address, arguments); err != nil { + return nil, errExecutionReverted + } + res, err := method.Outputs.Pack(g.state.LenDelegators(address)) + if err != nil { + return nil, errExecutionReverted + } + return res, nil case "nodesLength": - res, err := method.Outputs.Pack(g.state.NodesLength()) + res, err := method.Outputs.Pack(g.state.LenNodes()) if err != nil { return nil, errExecutionReverted } @@ -768,14 +944,20 @@ func RunGovernanceContract(evm *EVM, input []byte, contract *Contract) ( return nil, errExecutionReverted } return g.transferOwnership(newOwner) + case "undelegate": + address := common.Address{} + if err := method.Inputs.Unpack(&address, arguments); err != nil { + return nil, errExecutionReverted + } + return g.undelegate(address) case "unstake": return g.unstake() case "updateConfiguration": - var cfg params.DexconConfig + var cfg rawConfigStruct if err := method.Inputs.Unpack(&cfg, arguments); err != nil { return nil, errExecutionReverted } - g.updateConfiguration(&cfg) + return g.updateConfiguration(&cfg) // -------------------------------- // Solidity auto generated methods. @@ -803,6 +985,29 @@ func RunGovernanceContract(evm *EVM, input []byte, contract *Contract) ( return nil, errExecutionReverted } return res, nil + case "delegators": + nodeAddr, index := common.Address{}, new(big.Int) + args := []interface{}{&nodeAddr, &index} + if err := method.Inputs.Unpack(&args, arguments); err != nil { + return nil, errExecutionReverted + } + delegator := g.state.Delegator(nodeAddr, index) + res, err := method.Outputs.Pack(delegator.Owner, delegator.Value) + if err != nil { + return nil, errExecutionReverted + } + return res, nil + case "delegatorsOffset": + nodeAddr, delegatorAddr := common.Address{}, common.Address{} + args := []interface{}{&nodeAddr, &delegatorAddr} + if err := method.Inputs.Unpack(&args, arguments); err != nil { + return nil, errExecutionReverted + } + res, err := method.Outputs.Pack(g.state.DelegatorsOffset(nodeAddr, delegatorAddr)) + if err != nil { + return nil, errExecutionReverted + } + return res, nil case "dkgComplaints": round, index := new(big.Int), new(big.Int) args := []interface{}{&round, &index} @@ -913,18 +1118,18 @@ func RunGovernanceContract(evm *EVM, input []byte, contract *Contract) ( return nil, errExecutionReverted } return res, nil - case "notarySetSize": - res, err := method.Outputs.Pack(g.state.NotarySetSize()) - if err != nil { + case "nodesOffset": + address := common.Address{} + if err := method.Inputs.Unpack(&address, arguments); err != nil { return nil, errExecutionReverted } - return res, nil - case "offset": - addr := common.Address{} - if err := method.Inputs.Unpack(&addr, arguments); err != nil { + res, err := method.Outputs.Pack(g.state.NodesOffset(address)) + if err != nil { return nil, errExecutionReverted } - res, err := method.Outputs.Pack(g.state.Offset(addr)) + return res, nil + case "notarySetSize": + res, err := method.Outputs.Pack(g.state.NotarySetSize()) if err != nil { return nil, errExecutionReverted } @@ -965,7 +1170,9 @@ func RunGovernanceContract(evm *EVM, input []byte, contract *Contract) ( const ( roundHeightLoc = iota nodesLoc - offsetLoc + nodesOffsetLoc + delegatorsLoc + delegatorsOffsetLoc crsLoc dkgMasterPublicKeysLoc dkgComplaintsLoc @@ -1013,29 +1220,28 @@ func (s *GovernanceStateHelper) getSlotLoc(loc *big.Int) *big.Int { } func (s *GovernanceStateHelper) getMapLoc(pos *big.Int, key []byte) *big.Int { - return new(big.Int).SetBytes(crypto.Keccak256( - key, common.BigToHash(pos).Bytes())) + return new(big.Int).SetBytes(crypto.Keccak256(key, common.BigToHash(pos).Bytes())) } func (s *GovernanceStateHelper) readBytes(loc *big.Int) []byte { - // length of the dynamic array (bytes). + // Length of the dynamic array (bytes). rawLength := s.getStateBigInt(loc) lengthByte := new(big.Int).Mod(rawLength, big.NewInt(256)) - // bytes length <= 31, lengthByte % 2 == 0 + // Bytes length <= 31, lengthByte % 2 == 0 // return the high 31 bytes. if new(big.Int).Mod(lengthByte, big.NewInt(2)).Cmp(big.NewInt(0)) == 0 { length := new(big.Int).Div(lengthByte, big.NewInt(2)).Uint64() return rawLength.Bytes()[:length] } - // actual length = (rawLength - 1) / 2 + // Actual length = (rawLength - 1) / 2 length := new(big.Int).Div(new(big.Int).Sub(rawLength, big.NewInt(1)), big.NewInt(2)).Uint64() - // data address. + // Data address. dataLoc := s.getSlotLoc(loc) - // read continiously for length bytes. + // Read continuously for length bytes. carry := int64(0) if length%32 > 0 { carry = 1 @@ -1053,7 +1259,12 @@ func (s *GovernanceStateHelper) readBytes(loc *big.Int) []byte { func (s *GovernanceStateHelper) writeBytes(loc *big.Int, data []byte) { length := int64(len(data)) - // short bytes (length <= 31) + if length == 0 { + s.setState(common.BigToHash(loc), common.Hash{}) + return + } + + // Short bytes (length <= 31). if length < 32 { data2 := append([]byte(nil), data...) // Right pad with zeros @@ -1065,11 +1276,11 @@ func (s *GovernanceStateHelper) writeBytes(loc *big.Int, data []byte) { return } - // write 2 * length + 1 + // Write 2 * length + 1. storedLength := new(big.Int).Add(new(big.Int).Mul( big.NewInt(length), big.NewInt(2)), big.NewInt(1)) s.setStateBigInt(loc, storedLength) - // write data chunck. + // Write data chunck. dataLoc := s.getSlotLoc(loc) carry := int64(0) if length%32 > 0 { @@ -1083,7 +1294,7 @@ func (s *GovernanceStateHelper) writeBytes(loc *big.Int, data []byte) { maxLoc = length } data2 := data[i*32 : maxLoc] - // Right pad with zeros + // Right pad with zeros. for len(data2) < 32 { data2 = append(data2, byte(0)) } @@ -1111,11 +1322,11 @@ func (s *GovernanceStateHelper) appendTo2DByteArray(pos, index *big.Int, data [] baseLoc := s.getSlotLoc(pos) loc := new(big.Int).Add(baseLoc, index) - // increase length by 1. + // Increase length by 1. arrayLength := s.getStateBigInt(loc) s.setStateBigInt(loc, new(big.Int).Add(arrayLength, big.NewInt(1))) - // write element. + // Write element. dataLoc := s.getSlotLoc(loc) elementLoc := new(big.Int).Add(dataLoc, arrayLength) s.writeBytes(elementLoc, data) @@ -1131,7 +1342,7 @@ func (s *GovernanceStateHelper) RoundHeight(round *big.Int) *big.Int { return s.getStateBigInt(loc) } func (s *GovernanceStateHelper) PushRoundHeight(height *big.Int) { - // increase length by 1. + // Increase length by 1. length := s.getStateBigInt(big.NewInt(roundHeightLoc)) s.setStateBigInt(big.NewInt(roundHeightLoc), new(big.Int).Add(length, big.NewInt(1))) @@ -1163,9 +1374,9 @@ type nodeInfo struct { Url string } -const nodesInfoSize = 7 +const nodeStructSize = 7 -func (s *GovernanceStateHelper) NodesLength() *big.Int { +func (s *GovernanceStateHelper) LenNodes() *big.Int { return s.getStateBigInt(big.NewInt(nodesLoc)) } func (s *GovernanceStateHelper) Node(index *big.Int) *nodeInfo { @@ -1173,97 +1384,194 @@ func (s *GovernanceStateHelper) Node(index *big.Int) *nodeInfo { arrayBaseLoc := s.getSlotLoc(big.NewInt(nodesLoc)) elementBaseLoc := new(big.Int).Add(arrayBaseLoc, - new(big.Int).Mul(index, big.NewInt(nodesInfoSize))) + new(big.Int).Mul(index, big.NewInt(nodeStructSize))) - // owner. + // Owner. loc := elementBaseLoc node.Owner = common.BytesToAddress(s.getState(common.BigToHash(elementBaseLoc)).Bytes()) - // publicKey. + // PublicKey. loc = new(big.Int).Add(elementBaseLoc, big.NewInt(1)) node.PublicKey = s.readBytes(loc) - // staked. + // Staked. loc = new(big.Int).Add(elementBaseLoc, big.NewInt(2)) node.Staked = s.getStateBigInt(loc) - // name. + // Name. loc = new(big.Int).Add(elementBaseLoc, big.NewInt(3)) node.Name = string(s.readBytes(loc)) - // email. + // Email. loc = new(big.Int).Add(elementBaseLoc, big.NewInt(4)) node.Email = string(s.readBytes(loc)) - // location. + // Location. loc = new(big.Int).Add(elementBaseLoc, big.NewInt(5)) node.Location = string(s.readBytes(loc)) - // url. + // Url. loc = new(big.Int).Add(elementBaseLoc, big.NewInt(6)) node.Url = string(s.readBytes(loc)) return node } func (s *GovernanceStateHelper) PushNode(n *nodeInfo) { - // increase length by 1 - arrayLength := s.NodesLength() + // Increase length by 1. + arrayLength := s.LenNodes() s.setStateBigInt(big.NewInt(nodesLoc), new(big.Int).Add(arrayLength, big.NewInt(1))) s.UpdateNode(arrayLength, n) } func (s *GovernanceStateHelper) UpdateNode(index *big.Int, n *nodeInfo) { arrayBaseLoc := s.getSlotLoc(big.NewInt(nodesLoc)) - elementBaseLoc := new(big.Int).Add(arrayBaseLoc, - new(big.Int).Mul(index, big.NewInt(nodesInfoSize))) + elementBaseLoc := new(big.Int).Add(arrayBaseLoc, new(big.Int).Mul(index, big.NewInt(nodeStructSize))) - // owner. + // Owner. loc := elementBaseLoc s.setState(common.BigToHash(loc), n.Owner.Hash()) - // publicKey. + // PublicKey. loc = new(big.Int).Add(elementBaseLoc, big.NewInt(1)) s.writeBytes(loc, n.PublicKey) - // staked. + // Staked. loc = new(big.Int).Add(elementBaseLoc, big.NewInt(2)) s.setStateBigInt(loc, n.Staked) - // name. + // Name. loc = new(big.Int).Add(elementBaseLoc, big.NewInt(3)) s.writeBytes(loc, []byte(n.Name)) - // email. + // Email. loc = new(big.Int).Add(elementBaseLoc, big.NewInt(4)) s.writeBytes(loc, []byte(n.Email)) - // location. + // Location. loc = new(big.Int).Add(elementBaseLoc, big.NewInt(5)) s.writeBytes(loc, []byte(n.Location)) - // url. + // Url. loc = new(big.Int).Add(elementBaseLoc, big.NewInt(6)) s.writeBytes(loc, []byte(n.Url)) } +func (s *GovernanceStateHelper) PopLastNode() { + // Decrease length by 1. + arrayLength := s.LenNodes() + newArrayLength := new(big.Int).Sub(arrayLength, big.NewInt(1)) + s.setStateBigInt(big.NewInt(nodesLoc), newArrayLength) + + s.UpdateNode(newArrayLength, &nodeInfo{Staked: big.NewInt(0)}) +} func (s *GovernanceStateHelper) Nodes() []*nodeInfo { var nodes []*nodeInfo - for i := int64(0); i < int64(s.NodesLength().Uint64()); i++ { + for i := int64(0); i < int64(s.LenNodes().Uint64()); i++ { nodes = append(nodes, s.Node(big.NewInt(i))) } return nodes } +func (s *GovernanceStateHelper) QualifiedNodes() []*nodeInfo { + var nodes []*nodeInfo + for i := int64(0); i < int64(s.LenNodes().Uint64()); i++ { + node := s.Node(big.NewInt(i)) + if node.Staked.Cmp(s.MinStake()) >= 0 { + nodes = append(nodes, node) + } + } + return nodes +} -// mapping(address => uint256) public offset; -func (s *GovernanceStateHelper) Offset(addr common.Address) *big.Int { - loc := s.getMapLoc(big.NewInt(offsetLoc), addr.Bytes()) +// mapping(address => uint256) public nodeOffset; +func (s *GovernanceStateHelper) NodesOffset(addr common.Address) *big.Int { + loc := s.getMapLoc(big.NewInt(nodesOffsetLoc), addr.Bytes()) return new(big.Int).Sub(s.getStateBigInt(loc), big.NewInt(1)) } -func (s *GovernanceStateHelper) PutOffset(addr common.Address, offset *big.Int) { - loc := s.getMapLoc(big.NewInt(offsetLoc), addr.Bytes()) +func (s *GovernanceStateHelper) PutNodesOffset(addr common.Address, offset *big.Int) { + loc := s.getMapLoc(big.NewInt(nodesOffsetLoc), addr.Bytes()) s.setStateBigInt(loc, new(big.Int).Add(offset, big.NewInt(1))) } -func (s *GovernanceStateHelper) DeleteOffset(addr common.Address) { - loc := s.getMapLoc(big.NewInt(offsetLoc), addr.Bytes()) +func (s *GovernanceStateHelper) DeleteNodesOffset(addr common.Address) { + loc := s.getMapLoc(big.NewInt(nodesOffsetLoc), addr.Bytes()) + s.setStateBigInt(loc, big.NewInt(0)) +} + +// struct Delegator { +// address node; +// address owner; +// uint256 value; +// } + +type delegatorInfo struct { + Owner common.Address + Value *big.Int +} + +const delegatorStructSize = 2 + +// mapping(address => Delegator[]) public delegators; +func (s *GovernanceStateHelper) LenDelegators(nodeAddr common.Address) *big.Int { + loc := s.getMapLoc(big.NewInt(delegatorsLoc), nodeAddr.Bytes()) + return s.getStateBigInt(loc) +} +func (s *GovernanceStateHelper) Delegator(nodeAddr common.Address, offset *big.Int) *delegatorInfo { + delegator := new(delegatorInfo) + + loc := s.getMapLoc(big.NewInt(delegatorsLoc), nodeAddr.Bytes()) + arrayBaseLoc := s.getSlotLoc(loc) + elementBaseLoc := new(big.Int).Add(arrayBaseLoc, new(big.Int).Mul(big.NewInt(delegatorStructSize), offset)) + + // Owner. + loc = elementBaseLoc + delegator.Owner = common.BytesToAddress(s.getState(common.BigToHash(elementBaseLoc)).Bytes()) + + // Value. + loc = new(big.Int).Add(elementBaseLoc, big.NewInt(1)) + delegator.Value = s.getStateBigInt(loc) + + return delegator +} +func (s *GovernanceStateHelper) PushDelegator(nodeAddr common.Address, delegator *delegatorInfo) { + // Increase length by 1. + arrayLength := s.LenDelegators(nodeAddr) + loc := s.getMapLoc(big.NewInt(delegatorsLoc), nodeAddr.Bytes()) + s.setStateBigInt(loc, new(big.Int).Add(arrayLength, big.NewInt(1))) + + s.UpdateDelegator(nodeAddr, arrayLength, delegator) +} +func (s *GovernanceStateHelper) UpdateDelegator(nodeAddr common.Address, offset *big.Int, delegator *delegatorInfo) { + loc := s.getMapLoc(big.NewInt(delegatorsLoc), nodeAddr.Bytes()) + arrayBaseLoc := s.getSlotLoc(loc) + elementBaseLoc := new(big.Int).Add(arrayBaseLoc, new(big.Int).Mul(big.NewInt(delegatorStructSize), offset)) + + // Owner. + loc = elementBaseLoc + s.setState(common.BigToHash(loc), delegator.Owner.Hash()) + + // Value. + loc = new(big.Int).Add(elementBaseLoc, big.NewInt(1)) + s.setStateBigInt(loc, delegator.Value) +} +func (s *GovernanceStateHelper) PopLastDelegator(nodeAddr common.Address) { + // Decrease length by 1. + arrayLength := s.LenDelegators(nodeAddr) + newArrayLength := new(big.Int).Sub(arrayLength, big.NewInt(1)) + loc := s.getMapLoc(big.NewInt(delegatorsLoc), nodeAddr.Bytes()) + s.setStateBigInt(loc, newArrayLength) + + s.UpdateDelegator(nodeAddr, newArrayLength, &delegatorInfo{Value: big.NewInt(0)}) +} + +// mapping(address => mapping(address => uint256)) delegatorsOffset; +func (s *GovernanceStateHelper) DelegatorsOffset(nodeAddr, delegatorAddr common.Address) *big.Int { + loc := s.getMapLoc(s.getMapLoc(big.NewInt(delegatorsOffsetLoc), nodeAddr.Bytes()), delegatorAddr.Bytes()) + return new(big.Int).Sub(s.getStateBigInt(loc), big.NewInt(1)) +} +func (s *GovernanceStateHelper) PutDelegatorOffset(nodeAddr, delegatorAddr common.Address, offset *big.Int) { + loc := s.getMapLoc(s.getMapLoc(big.NewInt(delegatorsOffsetLoc), nodeAddr.Bytes()), delegatorAddr.Bytes()) + s.setStateBigInt(loc, new(big.Int).Add(offset, big.NewInt(1))) +} +func (s *GovernanceStateHelper) DeleteDelegatorsOffset(nodeAddr, delegatorAddr common.Address) { + loc := s.getMapLoc(s.getMapLoc(big.NewInt(delegatorsOffsetLoc), nodeAddr.Bytes()), delegatorAddr.Bytes()) s.setStateBigInt(loc, big.NewInt(0)) } @@ -1418,7 +1726,7 @@ func (s *GovernanceStateHelper) MinBlockInterval() *big.Int { func (s *GovernanceStateHelper) Stake( addr common.Address, publicKey []byte, staked *big.Int, name, email, location, url string) { - offset := s.NodesLength() + offset := s.LenNodes() s.PushNode(&nodeInfo{ Owner: addr, PublicKey: publicKey, @@ -1428,20 +1736,22 @@ func (s *GovernanceStateHelper) Stake( Location: location, Url: url, }) - s.PutOffset(addr, offset) + s.PutNodesOffset(addr, offset) } +const phiRatioMultiplier = 1000000.0 + // Configuration returns the current configuration. func (s *GovernanceStateHelper) Configuration() *params.DexconConfig { return ¶ms.DexconConfig{ MinStake: s.getStateBigInt(big.NewInt(minStakeLoc)), BlockReward: s.getStateBigInt(big.NewInt(blockRewardLoc)), - BlockGasLimit: uint64(s.getStateBigInt(big.NewInt(blockGasLimitLoc)).Uint64()), + BlockGasLimit: s.getStateBigInt(big.NewInt(blockGasLimitLoc)).Uint64(), NumChains: uint32(s.getStateBigInt(big.NewInt(numChainsLoc)).Uint64()), LambdaBA: s.getStateBigInt(big.NewInt(lambdaBALoc)).Uint64(), LambdaDKG: s.getStateBigInt(big.NewInt(lambdaDKGLoc)).Uint64(), - K: int(s.getStateBigInt(big.NewInt(kLoc)).Uint64()), - PhiRatio: float32(s.getStateBigInt(big.NewInt(phiRatioLoc)).Uint64()) / 1000000.0, + K: uint32(s.getStateBigInt(big.NewInt(kLoc)).Uint64()), + PhiRatio: float32(s.getStateBigInt(big.NewInt(phiRatioLoc)).Uint64()) / phiRatioMultiplier, NotarySetSize: uint32(s.getStateBigInt(big.NewInt(notarySetSizeLoc)).Uint64()), DKGSetSize: uint32(s.getStateBigInt(big.NewInt(dkgSetSizeLoc)).Uint64()), RoundInterval: s.getStateBigInt(big.NewInt(roundIntervalLoc)).Uint64(), @@ -1458,13 +1768,44 @@ func (s *GovernanceStateHelper) UpdateConfiguration(cfg *params.DexconConfig) { s.setStateBigInt(big.NewInt(lambdaBALoc), big.NewInt(int64(cfg.LambdaBA))) s.setStateBigInt(big.NewInt(lambdaDKGLoc), big.NewInt(int64(cfg.LambdaDKG))) s.setStateBigInt(big.NewInt(kLoc), big.NewInt(int64(cfg.K))) - s.setStateBigInt(big.NewInt(phiRatioLoc), big.NewInt(int64(cfg.PhiRatio))) + s.setStateBigInt(big.NewInt(phiRatioLoc), big.NewInt(int64(cfg.PhiRatio*phiRatioMultiplier))) s.setStateBigInt(big.NewInt(notarySetSizeLoc), big.NewInt(int64(cfg.NotarySetSize))) s.setStateBigInt(big.NewInt(dkgSetSizeLoc), big.NewInt(int64(cfg.DKGSetSize))) s.setStateBigInt(big.NewInt(roundIntervalLoc), big.NewInt(int64(cfg.RoundInterval))) s.setStateBigInt(big.NewInt(minBlockIntervalLoc), big.NewInt(int64(cfg.MinBlockInterval))) } +type rawConfigStruct struct { + MinStake *big.Int + BlockReward *big.Int + BlockGasLimit *big.Int + NumChains *big.Int + LambdaBA *big.Int + LambdaDKG *big.Int + K *big.Int + PhiRatio *big.Int + NotarySetSize *big.Int + DKGSetSize *big.Int + RoundInterval *big.Int + MinBlockInterval *big.Int +} + +// UpdateConfigurationRaw updates system configuration. +func (s *GovernanceStateHelper) UpdateConfigurationRaw(cfg *rawConfigStruct) { + s.setStateBigInt(big.NewInt(minStakeLoc), cfg.MinStake) + s.setStateBigInt(big.NewInt(blockRewardLoc), cfg.BlockReward) + s.setStateBigInt(big.NewInt(blockGasLimitLoc), cfg.BlockGasLimit) + s.setStateBigInt(big.NewInt(numChainsLoc), cfg.NumChains) + s.setStateBigInt(big.NewInt(lambdaBALoc), cfg.LambdaBA) + s.setStateBigInt(big.NewInt(lambdaDKGLoc), cfg.LambdaDKG) + s.setStateBigInt(big.NewInt(kLoc), cfg.K) + s.setStateBigInt(big.NewInt(phiRatioLoc), cfg.PhiRatio) + s.setStateBigInt(big.NewInt(notarySetSizeLoc), cfg.NotarySetSize) + s.setStateBigInt(big.NewInt(dkgSetSizeLoc), cfg.DKGSetSize) + s.setStateBigInt(big.NewInt(roundIntervalLoc), cfg.RoundInterval) + s.setStateBigInt(big.NewInt(minBlockIntervalLoc), cfg.MinBlockInterval) +} + // event ConfigurationChanged(); func (s *GovernanceStateHelper) emitConfigurationChangedEvent() { s.StateDB.AddLog(&types.Log{ @@ -1483,6 +1824,42 @@ func (s *GovernanceStateHelper) emitCRSProposed(round *big.Int, crs common.Hash) }) } +// event Staked(address indexed NodeAddress, uint256 Amount); +func (s *GovernanceStateHelper) emitStaked(nodeAddr common.Address) { + s.StateDB.AddLog(&types.Log{ + Address: GovernanceContractAddress, + Topics: []common.Hash{events["Staked"].Id(), nodeAddr.Hash()}, + Data: []byte{}, + }) +} + +// event Unstaked(address indexed NodeAddress); +func (s *GovernanceStateHelper) emitUnstaked(nodeAddr common.Address) { + s.StateDB.AddLog(&types.Log{ + Address: GovernanceContractAddress, + Topics: []common.Hash{events["Unstaked"].Id(), nodeAddr.Hash()}, + Data: []byte{}, + }) +} + +// event Delegated(address indexed NodeAddress, address indexed DelegatorAddress, uint256 Amount); +func (s *GovernanceStateHelper) emitDelegated(nodeAddr, delegatorAddr common.Address, amount *big.Int) { + s.StateDB.AddLog(&types.Log{ + Address: GovernanceContractAddress, + Topics: []common.Hash{events["Delegated"].Id(), nodeAddr.Hash(), delegatorAddr.Hash()}, + Data: common.BigToHash(amount).Bytes(), + }) +} + +// event Undelegated(address indexed NodeAddress, address indexed DelegatorAddress); +func (s *GovernanceStateHelper) emitUndelegated(nodeAddr, delegatorAddr common.Address) { + s.StateDB.AddLog(&types.Log{ + Address: GovernanceContractAddress, + Topics: []common.Hash{events["Undelegated"].Id(), nodeAddr.Hash(), delegatorAddr.Hash()}, + Data: []byte{}, + }) +} + // GovernanceContract represents the governance contract of DEXCON. type GovernanceContract struct { evm *EVM @@ -1498,7 +1875,20 @@ func newGovernanceContract(evm *EVM, contract *Contract) *GovernanceContract { } } -func (g *GovernanceContract) UseGas(gas uint64) ([]byte, error) { +func (g *GovernanceContract) Address() common.Address { + return GovernanceContractAddress +} + +func (g *GovernanceContract) transfer(from, to common.Address, amount *big.Int) bool { + // TODO(w): add this to debug trace so it shows up as internal transaction. + if g.evm.CanTransfer(g.evm.StateDB, from, amount) { + g.evm.Transfer(g.evm.StateDB, from, to, amount) + return true + } + return false +} + +func (g *GovernanceContract) useGas(gas uint64) ([]byte, error) { if !g.contract.UseGas(gas) { return nil, ErrOutOfGas } @@ -1506,7 +1896,7 @@ func (g *GovernanceContract) UseGas(gas uint64) ([]byte, error) { } func (g *GovernanceContract) penalize() ([]byte, error) { - g.UseGas(g.contract.Gas) + g.useGas(g.contract.Gas) return nil, errExecutionReverted } @@ -1519,13 +1909,13 @@ func (g *GovernanceContract) inDKGSet(round *big.Int, nodeID coreTypes.NodeID) b configRound = new(big.Int).Sub(round, big.NewInt(int64(core.ConfigRoundShift))) } - statedb, err := g.evm.Context.StateAtNumber(g.state.RoundHeight(configRound).Uint64()) + statedb, err := g.evm.StateAtNumber(g.state.RoundHeight(configRound).Uint64()) if err != nil { panic(err) } state := GovernanceStateHelper{statedb} - for _, x := range state.Nodes() { + for _, x := range state.QualifiedNodes() { mpk, err := ecdsa.NewPublicKeyFromByteSlice(x.PublicKey) if err != nil { panic(err) @@ -1578,7 +1968,7 @@ func (g *GovernanceContract) addDKGComplaint(round *big.Int, comp []byte) ([]byt g.state.PushDKGComplaint(round, comp) // Set this to relatively high to prevent spamming - return g.UseGas(5000000) + return g.useGas(5000000) } func (g *GovernanceContract) addDKGMasterPublicKey(round *big.Int, mpk []byte) ([]byte, error) { @@ -1588,7 +1978,7 @@ func (g *GovernanceContract) addDKGMasterPublicKey(round *big.Int, mpk []byte) ( } caller := g.contract.Caller() - offset := g.state.Offset(caller) + offset := g.state.NodesOffset(caller) // Can not add dkg mpk if not staked. if offset.Cmp(big.NewInt(0)) < 0 { @@ -1612,7 +2002,7 @@ func (g *GovernanceContract) addDKGMasterPublicKey(round *big.Int, mpk []byte) ( g.state.PushDKGMasterPublicKey(round, mpk) - return g.UseGas(100000) + return g.useGas(100000) } func (g *GovernanceContract) addDKGFinalize(round *big.Int, finalize []byte) ([]byte, error) { @@ -1642,16 +2032,48 @@ func (g *GovernanceContract) addDKGFinalize(round *big.Int, finalize []byte) ([] g.state.IncDKGFinalizedsCount(round) } - return g.UseGas(100000) + return g.useGas(100000) } -func (g *GovernanceContract) updateConfiguration(config *params.DexconConfig) ([]byte, error) { +func (g *GovernanceContract) delegate(nodeAddr common.Address) ([]byte, error) { + offset := g.state.NodesOffset(nodeAddr) + if offset.Cmp(big.NewInt(0)) < 0 { + return nil, errExecutionReverted + } + + caller := g.contract.Caller() + value := g.contract.Value() + + // Can not delegate if already delegated. + delegatorOffset := g.state.DelegatorsOffset(nodeAddr, caller) + if delegatorOffset.Cmp(big.NewInt(0)) >= 0 { + return nil, errExecutionReverted + } + + // Add to the total staked of node. + node := g.state.Node(offset) + node.Staked = new(big.Int).Add(node.Staked, g.contract.Value()) + g.state.UpdateNode(offset, node) + + // Push delegator record. + offset = g.state.LenDelegators(nodeAddr) + g.state.PushDelegator(nodeAddr, &delegatorInfo{ + Owner: caller, + Value: value, + }) + g.state.PutDelegatorOffset(nodeAddr, caller, offset) + g.state.emitDelegated(nodeAddr, caller, value) + + return g.useGas(200000) +} + +func (g *GovernanceContract) updateConfiguration(cfg *rawConfigStruct) ([]byte, error) { // Only owner can update configuration. if g.contract.Caller() != g.state.Owner() { return nil, errExecutionReverted } - g.state.UpdateConfiguration(config) + g.state.UpdateConfigurationRaw(cfg) g.state.emitConfigurationChangedEvent() return nil, nil } @@ -1665,57 +2087,116 @@ func (g *GovernanceContract) stake( } caller := g.contract.Caller() - offset := g.state.Offset(caller) + offset := g.state.NodesOffset(caller) - // Need to stake at least minStake. - if g.contract.Value().Cmp(g.state.MinStake()) < 0 { + // Check if public key is valid. + if _, err := crypto.UnmarshalPubkey(publicKey); err != nil { return g.penalize() } // Can not stake if already staked. if offset.Cmp(big.NewInt(0)) >= 0 { - return g.penalize() + return nil, errExecutionReverted } - offset = g.state.NodesLength() + offset = g.state.LenNodes() g.state.PushNode(&nodeInfo{ Owner: caller, PublicKey: publicKey, - Staked: g.contract.Value(), + Staked: big.NewInt(0), Name: name, Email: email, Location: location, Url: url, }) - g.state.PutOffset(caller, offset) + g.state.PutNodesOffset(caller, offset) - return g.UseGas(0) + // Delegate fund to itself. + if g.contract.Value().Cmp(big.NewInt(0)) > 0 { + if ret, err := g.delegate(caller); err != nil { + return ret, err + } + } + + g.state.emitStaked(caller) + return g.useGas(100000) +} + +func (g *GovernanceContract) undelegateHelper(nodeAddr, owner 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) + if offset.Cmp(big.NewInt(0)) < 0 { + return nil, errExecutionReverted + } + + delegator := g.state.Delegator(nodeAddr, offset) + length := g.state.LenDelegators(nodeAddr) + lastIndex := new(big.Int).Sub(length, big.NewInt(1)) + + // Delete the delegator. + if offset.Cmp(lastIndex) != 0 { + lastNode := g.state.Delegator(nodeAddr, lastIndex) + g.state.UpdateDelegator(nodeAddr, offset, lastNode) + g.state.PutDelegatorOffset(nodeAddr, lastNode.Owner, offset) + } + g.state.DeleteDelegatorsOffset(nodeAddr, owner) + 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) +} + +func (g *GovernanceContract) undelegate(nodeAddr common.Address) ([]byte, error) { + return g.undelegateHelper(nodeAddr, g.contract.Caller()) } func (g *GovernanceContract) unstake() ([]byte, error) { caller := g.contract.Caller() - offset := g.state.Offset(caller) + offset := g.state.NodesOffset(caller) if offset.Cmp(big.NewInt(0)) < 0 { return nil, errExecutionReverted } - node := g.state.Node(offset) - length := g.state.NodesLength() + // Undelegate all delegators. + lenDelegators := g.state.LenDelegators(caller) + i := new(big.Int).Sub(lenDelegators, big.NewInt(1)) + for i.Cmp(big.NewInt(0)) >= 0 { + delegator := g.state.Delegator(caller, i) + if ret, err := g.undelegateHelper(caller, delegator.Owner); err != nil { + return ret, err + } + 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 != lastIndex { + if offset.Cmp(lastIndex) != 0 { lastNode := g.state.Node(lastIndex) g.state.UpdateNode(offset, lastNode) - g.state.PutOffset(lastNode.Owner, offset) - g.state.DeleteOffset(caller) + g.state.PutNodesOffset(lastNode.Owner, offset) } + g.state.DeleteNodesOffset(caller) + g.state.PopLastNode() - // Return the staked fund. - // TODO(w): add this to debug trace so it shows up as internal transaction. - g.evm.Transfer(g.evm.StateDB, GovernanceContractAddress, caller, node.Staked) + g.state.emitUnstaked(caller) - return g.UseGas(0) + return g.useGas(100000) } func (g *GovernanceContract) proposeCRS(nextRound *big.Int, signedCRS []byte) ([]byte, error) { @@ -1774,11 +2255,11 @@ func (g *GovernanceContract) proposeCRS(nextRound *big.Int, signedCRS []byte) ([ crs := common.BytesToHash(newCRS) g.state.PushCRS(crs) - g.state.emitCRSProposed(g.state.LenCRS(), crs) + g.state.emitCRSProposed(nextRound, crs) // To encourage DKG set to propose the correct value, correctly submitting // this should cause nothing. - return g.UseGas(0) + return g.useGas(0) } func (g *GovernanceContract) transferOwnership(newOwner common.Address) ([]byte, error) { @@ -1793,7 +2274,7 @@ func (g *GovernanceContract) transferOwnership(newOwner common.Address) ([]byte, func (g *GovernanceContract) snapshotRound(round, height *big.Int) ([]byte, error) { // Validate if this mapping is correct. Only block proposer need to verify this. if g.evm.IsBlockProposer() { - realHeight, ok := g.evm.Context.GetRoundHeight(round.Uint64()) + realHeight, ok := g.evm.GetRoundHeight(round.Uint64()) if !ok { return g.penalize() } diff --git a/core/vm/governance_test.go b/core/vm/governance_test.go index ebdca7fd8..7718e99d2 100644 --- a/core/vm/governance_test.go +++ b/core/vm/governance_test.go @@ -19,6 +19,7 @@ package vm import ( "bytes" + "crypto/ecdsa" "math/big" "math/rand" "testing" @@ -26,7 +27,9 @@ import ( "github.com/dexon-foundation/dexon/common" "github.com/dexon-foundation/dexon/core/state" + "github.com/dexon-foundation/dexon/crypto" "github.com/dexon-foundation/dexon/ethdb" + "github.com/dexon-foundation/dexon/params" "github.com/stretchr/testify/suite" ) @@ -79,3 +82,557 @@ func (g *GovernanceStateHelperTestSuite) TestReadWriteBytes() { func TestGovernanceStateHelper(t *testing.T) { suite.Run(t, new(GovernanceStateHelperTestSuite)) } + +type GovernanceContractTestSuite struct { + suite.Suite + + config *params.DexconConfig + memDB *ethdb.MemDatabase + stateDB *state.StateDB + s *GovernanceStateHelper +} + +func (g *GovernanceContractTestSuite) SetupTest() { + memDB := ethdb.NewMemDatabase() + stateDB, err := state.New(common.Hash{}, state.NewDatabase(memDB)) + if err != nil { + panic(err) + } + g.memDB = memDB + g.stateDB = stateDB + g.s = &GovernanceStateHelper{stateDB} + + config := params.TestnetChainConfig.Dexcon + g.config = config + + // Give governance contract balance so it will not be deleted because of being an empty state object. + stateDB.AddBalance(GovernanceContractAddress, big.NewInt(1)) + + // Genesis CRS. + crs := crypto.Keccak256Hash([]byte(config.GenesisCRSText)) + g.s.PushCRS(crs) + + // Round 0 height. + g.s.PushRoundHeight(big.NewInt(0)) + + // Owner. + g.s.SetOwner(g.config.Owner) + + // Governance configuration. + g.s.UpdateConfiguration(config) + + g.stateDB.Commit(true) +} + +func (g *GovernanceContractTestSuite) newPrefundAccount() (*ecdsa.PrivateKey, common.Address) { + privKey, err := crypto.GenerateKey() + if err != nil { + panic(err) + } + address := crypto.PubkeyToAddress(privKey.PublicKey) + + g.stateDB.AddBalance(address, new(big.Int).Mul(big.NewInt(1e18), big.NewInt(2e5))) + return privKey, address +} + +func (g *GovernanceContractTestSuite) call(caller common.Address, input []byte, value *big.Int) ([]byte, error) { + context := Context{ + CanTransfer: func(db StateDB, addr common.Address, amount *big.Int) bool { + return db.GetBalance(addr).Cmp(amount) >= 0 + }, + Transfer: func(db StateDB, sender common.Address, recipient common.Address, amount *big.Int) { + db.SubBalance(sender, amount) + db.AddBalance(recipient, amount) + }, + GetRoundHeight: func(round uint64) (uint64, bool) { + switch round { + case 0: + return 0, true + case 1: + return 1000, true + case 2: + return 2000, true + } + return 0, false + }, + Time: big.NewInt(time.Now().UnixNano() / 1000000000), + BlockNumber: big.NewInt(0), + } + + evm := NewEVM(context, g.stateDB, params.TestChainConfig, Config{IsBlockProposer: true}) + ret, _, err := evm.Call(AccountRef(caller), GovernanceContractAddress, input, 10000000, value) + return ret, err +} + +func (g *GovernanceContractTestSuite) TestTransferOwnership() { + _, addr := g.newPrefundAccount() + + input, err := abiObject.Pack("transferOwnership", addr) + g.Require().Nil(err) + + // Call with non-owner. + _, err = g.call(addr, input, big.NewInt(0)) + g.Require().NotNil(err) + + // Call with owner. + _, err = g.call(g.config.Owner, input, big.NewInt(0)) + g.Require().Nil(err) + g.Require().Equal(addr, g.s.Owner()) +} + +func (g *GovernanceContractTestSuite) TestStakeUnstakeWithoutDelegators() { + privKey, addr := g.newPrefundAccount() + pk := crypto.FromECDSAPub(&privKey.PublicKey) + + // Stake. + amount := new(big.Int).Mul(big.NewInt(1e18), big.NewInt(5e4)) + balanceBeforeStake := g.stateDB.GetBalance(addr) + input, err := abiObject.Pack("stake", pk, "Test1", "test1@dexon.org", "Taipei, Taiwan", "https://dexon.org") + g.Require().Nil(err) + _, err = g.call(addr, input, amount) + g.Require().Nil(err) + + // Node staked but staked fund < MinStake so is still unqualified. + g.Require().Equal(1, int(g.s.LenNodes().Uint64())) + g.Require().Equal(0, len(g.s.QualifiedNodes())) + g.Require().Equal("Test1", g.s.Node(big.NewInt(0)).Name) + + // Check balance. + g.Require().Equal(new(big.Int).Sub(balanceBeforeStake, amount), g.stateDB.GetBalance(addr)) + g.Require().Equal(new(big.Int).Add(big.NewInt(1), amount), g.stateDB.GetBalance(GovernanceContractAddress)) + + // Staking again should fail. + _, err = g.call(addr, input, amount) + g.Require().NotNil(err) + + // Unstake. + input, err = abiObject.Pack("unstake") + 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())) + + // Stake 2 nodes, and unstake the first then the second. + + // 2nd node Stake. + privKey2, addr2 := g.newPrefundAccount() + pk2 := crypto.FromECDSAPub(&privKey2.PublicKey) + input, err = abiObject.Pack("stake", pk2, "Test2", "test2@dexon.org", "Taipei, Taiwan", "https://dexon.org") + g.Require().Nil(err) + _, err = g.call(addr2, input, amount) + g.Require().Nil(err) + g.Require().Equal("Test2", g.s.Node(big.NewInt(0)).Name) + g.Require().Equal(0, int(g.s.NodesOffset(addr2).Int64())) + + // 1st node Stake. + input, err = abiObject.Pack("stake", pk, "Test1", "test1@dexon.org", "Taipei, Taiwan", "https://dexon.org") + g.Require().Nil(err) + _, err = g.call(addr, input, amount) + g.Require().Nil(err) + + // 2nd node Unstake. + input, err = abiObject.Pack("unstake") + 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())) + + // 1st node Unstake. + input, err = abiObject.Pack("unstake") + 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())) + + // Check balance. + g.Require().Equal(balanceBeforeStake, g.stateDB.GetBalance(addr)) + g.Require().Equal(balanceBeforeStake, g.stateDB.GetBalance(addr2)) + g.Require().Equal(big.NewInt(1), g.stateDB.GetBalance(GovernanceContractAddress)) +} + +func (g *GovernanceContractTestSuite) TestDelegateUndelegate() { + privKey, addr := g.newPrefundAccount() + pk := crypto.FromECDSAPub(&privKey.PublicKey) + + // Stake. + input, err := abiObject.Pack("stake", pk, "Test1", "test1@dexon.org", "Taipei, Taiwan", "https://dexon.org") + g.Require().Nil(err) + ownerStaked := new(big.Int).Mul(big.NewInt(1e18), big.NewInt(5e4)) + _, err = g.call(addr, input, ownerStaked) + g.Require().Nil(err) + g.Require().Equal(0, len(g.s.QualifiedNodes())) + g.Require().Equal(addr, g.s.Delegator(addr, big.NewInt(0)).Owner) + g.Require().Equal(ownerStaked, g.s.Node(big.NewInt(0)).Staked) + + // 1st delegator delegate to 1st node. + _, addrDelegator := g.newPrefundAccount() + + balanceBeforeDelegate := g.stateDB.GetBalance(addrDelegator) + amount := new(big.Int).Mul(big.NewInt(1e18), big.NewInt(3e4)) + input, err = abiObject.Pack("delegate", addr) + g.Require().Nil(err) + + _, err = g.call(addrDelegator, input, amount) + g.Require().Nil(err) + g.Require().Equal(new(big.Int).Sub(balanceBeforeDelegate, amount), g.stateDB.GetBalance(addrDelegator)) + g.Require().Equal(addrDelegator, g.s.Delegator(addr, big.NewInt(1)).Owner) + g.Require().Equal(new(big.Int).Add(amount, ownerStaked), g.s.Node(big.NewInt(0)).Staked) + g.Require().Equal(1, int(g.s.DelegatorsOffset(addr, addrDelegator).Int64())) + + // Same person delegate the 2nd time should fail. + _, err = g.call(addrDelegator, input, big.NewInt(1e18)) + g.Require().NotNil(err) + + // Not yet qualified. + g.Require().Equal(0, len(g.s.QualifiedNodes())) + + // 2nd delegator delegate to 1st node. + _, addrDelegator2 := g.newPrefundAccount() + _, err = g.call(addrDelegator2, input, amount) + g.Require().Nil(err) + g.Require().Equal(new(big.Int).Sub(balanceBeforeDelegate, amount), g.stateDB.GetBalance(addrDelegator2)) + g.Require().Equal(addrDelegator2, g.s.Delegator(addr, big.NewInt(2)).Owner) + g.Require().Equal(new(big.Int).Add(ownerStaked, new(big.Int).Mul(amount, big.NewInt(2))), + g.s.Node(big.NewInt(0)).Staked) + g.Require().Equal(2, int(g.s.DelegatorsOffset(addr, addrDelegator2).Int64())) + + // Qualified. + g.Require().Equal(1, len(g.s.QualifiedNodes())) + + // Undelegate addrDelegator. + balanceBeforeUnDelegate := g.stateDB.GetBalance(addrDelegator) + input, err = abiObject.Pack("undelegate", 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())) + + // 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) + 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())) + + // Unqualified + g.Require().Equal(0, len(g.s.QualifiedNodes())) +} + +func (g *GovernanceContractTestSuite) TestUnstakeWithDelegators() { + privKey, addr := g.newPrefundAccount() + pk := crypto.FromECDSAPub(&privKey.PublicKey) + + // Stake. + amount := new(big.Int).Mul(big.NewInt(1e18), big.NewInt(5e4)) + input, err := abiObject.Pack("stake", pk, "Test1", "test1@dexon.org", "Taipei, Taiwan", "https://dexon.org") + g.Require().Nil(err) + _, err = g.call(addr, input, amount) + g.Require().Nil(err) + + // 1st delegator delegate to 1st node. + _, addrDelegator := g.newPrefundAccount() + + balanceBeforeDelegate := g.stateDB.GetBalance(addrDelegator) + amount = new(big.Int).Mul(big.NewInt(1e18), big.NewInt(3e4)) + input, err = abiObject.Pack("delegate", addr) + g.Require().Nil(err) + + _, err = g.call(addrDelegator, input, amount) + g.Require().Nil(err) + g.Require().Equal(new(big.Int).Sub(balanceBeforeDelegate, amount), g.stateDB.GetBalance(addrDelegator)) + g.Require().Equal(addrDelegator, g.s.Delegator(addr, big.NewInt(1)).Owner) + g.Require().Equal(0, len(g.s.QualifiedNodes())) + + // 2st delegator delegate to 1st node. + _, addrDelegator2 := g.newPrefundAccount() + + balanceBeforeDelegate = g.stateDB.GetBalance(addrDelegator2) + input, err = abiObject.Pack("delegate", addr) + g.Require().Nil(err) + + _, err = g.call(addrDelegator2, input, amount) + g.Require().Nil(err) + g.Require().Equal(new(big.Int).Sub(balanceBeforeDelegate, amount), g.stateDB.GetBalance(addrDelegator2)) + g.Require().Equal(addrDelegator2, g.s.Delegator(addr, big.NewInt(2)).Owner) + + // Node is now qualified. + g.Require().Equal(1, len(g.s.QualifiedNodes())) + + // Unstake. + input, err = abiObject.Pack("unstake") + 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())) + + // Check balance. + g.Require().Equal(balanceBeforeDelegate, g.stateDB.GetBalance(addr)) + g.Require().Equal(balanceBeforeDelegate, g.stateDB.GetBalance(addrDelegator)) + g.Require().Equal(balanceBeforeDelegate, g.stateDB.GetBalance(addrDelegator2)) + g.Require().Equal(big.NewInt(1), g.stateDB.GetBalance(GovernanceContractAddress)) +} + +func (g *GovernanceContractTestSuite) TestUpdateConfiguration() { + _, addr := g.newPrefundAccount() + + input, err := abiObject.Pack("updateConfiguration", + new(big.Int).Mul(big.NewInt(1e18), big.NewInt(1e5)), + 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) + + // Call with non-owner. + _, err = g.call(addr, input, big.NewInt(0)) + g.Require().NotNil(err) + + // Call with owner. + _, err = g.call(g.config.Owner, input, big.NewInt(0)) + g.Require().Nil(err) +} + +func (g *GovernanceContractTestSuite) TestSnapshotRound() { + _, addr := g.newPrefundAccount() + + // Wrong height. + input, err := abiObject.Pack("snapshotRound", big.NewInt(1), big.NewInt(666)) + g.Require().Nil(err) + _, err = g.call(addr, input, big.NewInt(0)) + g.Require().NotNil(err) + + // Invalid round. + input, err = abiObject.Pack("snapshotRound", big.NewInt(2), big.NewInt(2000)) + g.Require().Nil(err) + _, err = g.call(addr, input, big.NewInt(0)) + g.Require().NotNil(err) + + // Correct. + input, err = abiObject.Pack("snapshotRound", big.NewInt(1), big.NewInt(1000)) + g.Require().Nil(err) + _, err = g.call(addr, input, big.NewInt(0)) + g.Require().Nil(err) + + // Duplicate round. + input, err = abiObject.Pack("snapshotRound", big.NewInt(1), big.NewInt(1000)) + g.Require().Nil(err) + _, err = g.call(addr, input, big.NewInt(0)) + g.Require().NotNil(err) + + // Invalid round. + input, err = abiObject.Pack("snapshotRound", big.NewInt(3), big.NewInt(3000)) + g.Require().Nil(err) + _, err = g.call(addr, input, big.NewInt(0)) + g.Require().NotNil(err) + + // Correct. + input, err = abiObject.Pack("snapshotRound", big.NewInt(2), big.NewInt(2000)) + g.Require().Nil(err) + _, err = g.call(addr, input, big.NewInt(0)) + g.Require().Nil(err) +} + +func (g *GovernanceContractTestSuite) TestConfigurationReading() { + _, addr := g.newPrefundAccount() + + // CRS. + input, err := abiObject.Pack("crs", big.NewInt(0)) + g.Require().Nil(err) + res, err := g.call(addr, input, big.NewInt(0)) + g.Require().Nil(err) + var crs0 [32]byte + err = abiObject.Unpack(&crs0, "crs", res) + g.Require().Nil(err) + g.Require().Equal(crypto.Keccak256Hash([]byte(g.config.GenesisCRSText)), common.BytesToHash(crs0[:])) + + // Owner. + input, err = abiObject.Pack("owner") + g.Require().Nil(err) + res, err = g.call(addr, input, big.NewInt(0)) + g.Require().Nil(err) + var owner common.Address + err = abiObject.Unpack(&owner, "owner", res) + g.Require().Nil(err) + g.Require().Equal(g.config.Owner, owner) + + // MinStake. + input, err = abiObject.Pack("minStake") + g.Require().Nil(err) + res, err = g.call(addr, input, big.NewInt(0)) + g.Require().Nil(err) + var value *big.Int + err = abiObject.Unpack(&value, "minStake", res) + g.Require().Nil(err) + g.Require().Equal(g.config.MinStake.String(), value.String()) + + // BlockReward. + input, err = abiObject.Pack("blockReward") + g.Require().Nil(err) + res, err = g.call(addr, input, big.NewInt(0)) + g.Require().Nil(err) + err = abiObject.Unpack(&value, "blockReward", res) + g.Require().Nil(err) + g.Require().Equal(g.config.BlockReward.String(), value.String()) + + // BlockGasLimit. + input, err = abiObject.Pack("blockGasLimit") + g.Require().Nil(err) + res, err = g.call(addr, input, big.NewInt(0)) + g.Require().Nil(err) + err = abiObject.Unpack(&value, "blockGasLimit", res) + g.Require().Nil(err) + g.Require().Equal(g.config.BlockGasLimit, value.Uint64()) + + // NumChains. + input, err = abiObject.Pack("numChains") + g.Require().Nil(err) + res, err = g.call(addr, input, big.NewInt(0)) + g.Require().Nil(err) + err = abiObject.Unpack(&value, "numChains", res) + g.Require().Nil(err) + g.Require().Equal(g.config.NumChains, uint32(value.Uint64())) + + // LambdaBA. + input, err = abiObject.Pack("lambdaBA") + g.Require().Nil(err) + res, err = g.call(addr, input, big.NewInt(0)) + g.Require().Nil(err) + err = abiObject.Unpack(&value, "lambdaBA", res) + g.Require().Nil(err) + g.Require().Equal(g.config.LambdaBA, value.Uint64()) + + // LambdaDKG. + input, err = abiObject.Pack("lambdaDKG") + g.Require().Nil(err) + res, err = g.call(addr, input, big.NewInt(0)) + g.Require().Nil(err) + err = abiObject.Unpack(&value, "lambdaDKG", res) + g.Require().Nil(err) + g.Require().Equal(g.config.LambdaDKG, value.Uint64()) + + // K. + input, err = abiObject.Pack("k") + g.Require().Nil(err) + res, err = g.call(addr, input, big.NewInt(0)) + g.Require().Nil(err) + err = abiObject.Unpack(&value, "k", res) + g.Require().Nil(err) + g.Require().Equal(g.config.K, uint32(value.Uint64())) + + // PhiRatio. + input, err = abiObject.Pack("phiRatio") + g.Require().Nil(err) + res, err = g.call(addr, input, big.NewInt(0)) + g.Require().Nil(err) + err = abiObject.Unpack(&value, "phiRatio", res) + g.Require().Nil(err) + g.Require().Equal(g.config.PhiRatio, float32(value.Uint64())/phiRatioMultiplier) + + // NotarySetSize. + input, err = abiObject.Pack("notarySetSize") + g.Require().Nil(err) + res, err = g.call(addr, input, big.NewInt(0)) + g.Require().Nil(err) + err = abiObject.Unpack(&value, "notarySetSize", res) + g.Require().Nil(err) + g.Require().Equal(g.config.NotarySetSize, uint32(value.Uint64())) + + // DKGSetSize. + input, err = abiObject.Pack("dkgSetSize") + g.Require().Nil(err) + res, err = g.call(addr, input, big.NewInt(0)) + g.Require().Nil(err) + err = abiObject.Unpack(&value, "dkgSetSize", res) + g.Require().Nil(err) + g.Require().Equal(g.config.DKGSetSize, uint32(value.Uint64())) + + // RoundInterval. + input, err = abiObject.Pack("roundInterval") + g.Require().Nil(err) + res, err = g.call(addr, input, big.NewInt(0)) + g.Require().Nil(err) + err = abiObject.Unpack(&value, "roundInterval", res) + g.Require().Nil(err) + g.Require().Equal(g.config.RoundInterval, value.Uint64()) + + // MinBlockInterval. + input, err = abiObject.Pack("minBlockInterval") + g.Require().Nil(err) + res, err = g.call(addr, input, big.NewInt(0)) + g.Require().Nil(err) + err = abiObject.Unpack(&value, "minBlockInterval", res) + g.Require().Nil(err) + g.Require().Equal(g.config.MinBlockInterval, value.Uint64()) +} + +func (g *GovernanceContractTestSuite) TestMiscVariableReading() { + privKey, addr := g.newPrefundAccount() + pk := crypto.FromECDSAPub(&privKey.PublicKey) + + // Stake. + amount := new(big.Int).Mul(big.NewInt(1e18), big.NewInt(5e4)) + input, err := abiObject.Pack("stake", pk, "Test1", "test1@dexon.org", "Taipei, Taiwan", "https://dexon.org") + g.Require().Nil(err) + _, err = g.call(addr, input, amount) + g.Require().Nil(err) + + // 1st delegator delegate to 1st node. + _, addrDelegator := g.newPrefundAccount() + amount = new(big.Int).Mul(big.NewInt(1e18), big.NewInt(3e4)) + input, err = abiObject.Pack("delegate", addr) + g.Require().Nil(err) + _, err = g.call(addrDelegator, input, amount) + g.Require().Nil(err) + + // 2st delegator delegate to 1st node. + _, addrDelegator2 := g.newPrefundAccount() + input, err = abiObject.Pack("delegate", addr) + g.Require().Nil(err) + _, err = g.call(addrDelegator2, input, amount) + g.Require().Nil(err) + + input, err = abiObject.Pack("nodesLength") + g.Require().Nil(err) + res, err := g.call(addr, input, big.NewInt(0)) + g.Require().Nil(err) + var value *big.Int + err = abiObject.Unpack(&value, "nodesLength", res) + g.Require().Nil(err) + g.Require().Equal(1, int(value.Uint64())) + + input, err = abiObject.Pack("nodesOffset", addr) + g.Require().Nil(err) + res, err = g.call(addr, input, big.NewInt(0)) + g.Require().Nil(err) + err = abiObject.Unpack(&value, "nodesOffset", res) + g.Require().Nil(err) + g.Require().Equal(0, int(value.Uint64())) + + input, err = abiObject.Pack("delegatorsLength", addr) + g.Require().Nil(err) + res, err = g.call(addr, input, big.NewInt(0)) + g.Require().Nil(err) + err = abiObject.Unpack(&value, "delegatorsLength", res) + g.Require().Nil(err) + g.Require().Equal(3, int(value.Uint64())) + + input, err = abiObject.Pack("delegatorsOffset", addr, addrDelegator2) + g.Require().Nil(err) + res, err = g.call(addr, input, big.NewInt(0)) + g.Require().Nil(err) + err = abiObject.Unpack(&value, "delegatorsOffset", res) + g.Require().Nil(err) + g.Require().Equal(2, int(value.Uint64())) +} + +func TestGovernanceContract(t *testing.T) { + suite.Run(t, new(GovernanceContractTestSuite)) +} |