aboutsummaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
authorWei-Ning Huang <w@dexon.org>2018-11-19 14:36:36 +0800
committerWei-Ning Huang <w@dexon.org>2019-03-12 12:19:09 +0800
commit9181d784fb453423c1a7dafe7c182feafd437376 (patch)
tree889f51b8751b272c8f6a39a4d9e6d34cf671c4b9 /core
parentbe1f06a3acfc85f90a2a9f6dec3235b5bf1f0099 (diff)
downloaddexon-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.go7
-rw-r--r--core/genesis.go4
-rw-r--r--core/vm/governance.go695
-rw-r--r--core/vm/governance_test.go557
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 &params.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))
+}