github.com/DapperCollectives/CAST/backend@v0.0.0-20230921221157-1350c8be7c96/tests/strategies_test.go (about)

     1  package main
     2  
     3  import (
     4  	"encoding/json"
     5  	"math"
     6  	"net/http"
     7  	"testing"
     8  
     9  	"github.com/DapperCollectives/CAST/backend/main/models"
    10  	"github.com/DapperCollectives/CAST/backend/main/shared"
    11  	"github.com/DapperCollectives/CAST/backend/main/strategies"
    12  	utils "github.com/DapperCollectives/CAST/backend/tests/test_utils"
    13  	"github.com/stretchr/testify/assert"
    14  )
    15  
    16  type Strategy interface {
    17  	TallyVotes(votes []*models.VoteWithBalance, p *models.ProposalResults, proposal *models.Proposal) (models.ProposalResults, error)
    18  	GetVotes(votes []*models.VoteWithBalance, proposal *models.Proposal) ([]*models.VoteWithBalance, error)
    19  	GetVoteWeightForBalance(vote *models.VoteWithBalance, proposal *models.Proposal) (float64, error)
    20  	InitStrategy(f *shared.FlowAdapter, db *shared.Database)
    21  	FetchBalance(b *models.Balance, p *models.Proposal) (*models.Balance, error)
    22  	RequiresSnapshot() bool
    23  }
    24  
    25  var strategyMap = map[string]Strategy{
    26  	"token-weighted-default":        &strategies.TokenWeightedDefault{},
    27  	"staked-token-weighted-default": &strategies.StakedTokenWeightedDefault{},
    28  	"one-address-one-vote":          &strategies.OneAddressOneVote{},
    29  	"balance-of-nfts":               &strategies.BalanceOfNfts{},
    30  }
    31  
    32  /* Token Weighted Default */
    33  func TestTokenWeightedDefaultStrategy(t *testing.T) {
    34  	clearTable("communities")
    35  	clearTable("community_users")
    36  	clearTable("proposals")
    37  	clearTable("votes")
    38  	clearTable("balances")
    39  
    40  	communityId := otu.AddCommunities(1, "dao")[0]
    41  	proposalIds, proposals := otu.AddProposalsForStrategy(communityId, "token-weighted-default", 2)
    42  	votes := otu.GenerateListOfVotes(proposalIds[0], 10)
    43  	proposalId := proposalIds[0]
    44  	proposalIdTwo := proposalIds[1]
    45  	choices := proposals[0].Choices
    46  
    47  	err := otu.AddDummyVotesAndBalances(votes)
    48  	if err != nil {
    49  		t.Errorf("Error adding votes and balances: %s", err)
    50  	}
    51  
    52  	t.Run("Test Tallying Results", func(t *testing.T) {
    53  		// Tally results
    54  		strategyName := "token-weighted-default"
    55  
    56  		s := strategyMap[strategyName]
    57  		proposalWithChoices := models.NewProposalResults(proposalId, choices)
    58  		_results, err := s.TallyVotes(votes, proposalWithChoices, proposals[0])
    59  		if err != nil {
    60  			t.Errorf("Error tallying votes: %v", err)
    61  		}
    62  
    63  		// Fetch Proposal Results
    64  		response := otu.GetProposalResultsAPI(proposalId)
    65  		CheckResponseCode(t, http.StatusOK, response.Code)
    66  
    67  		var results models.ProposalResults
    68  		json.Unmarshal(response.Body.Bytes(), &results)
    69  
    70  		assert.Equal(t, _results.Proposal_id, results.Proposal_id)
    71  		assert.Equal(t, _results.Results_float["a"], results.Results_float["a"])
    72  		assert.Equal(t, _results.Results_float["b"], results.Results_float["b"])
    73  	})
    74  
    75  	t.Run("Test Fetching Votes for Proposal", func(t *testing.T) {
    76  		response := otu.GetVotesForProposalAPI(proposalId)
    77  
    78  		CheckResponseCode(t, http.StatusOK, response.Code)
    79  
    80  		var body utils.PaginatedResponseWithVotes
    81  		json.Unmarshal(response.Body.Bytes(), &body)
    82  
    83  		// Validate vote weights are returned correctly
    84  		for i, v := range body.Data {
    85  			_vote := (votes)[i]
    86  			expectedWeight := float64(*_vote.PrimaryAccountBalance) * math.Pow(10, -8)
    87  			assert.Equal(t, expectedWeight, *v.Weight)
    88  		}
    89  	})
    90  
    91  	t.Run("Test Fetching Vote for Address", func(t *testing.T) {
    92  		_vote := (votes)[0]
    93  		response := otu.GetVoteForProposalByAddressAPI(proposalId, _vote.Addr)
    94  
    95  		CheckResponseCode(t, http.StatusOK, response.Code)
    96  
    97  		var vote models.VoteWithBalance
    98  		json.Unmarshal(response.Body.Bytes(), &vote)
    99  
   100  		_expectedWeight := float64(*_vote.PrimaryAccountBalance) * math.Pow(10, -8)
   101  		assert.Equal(t, _expectedWeight, *vote.Weight)
   102  	})
   103  
   104  	t.Run("Test Fetching Votes For Address", func(t *testing.T) {
   105  		_vote := (votes)[0]
   106  		proposalIds := []int{proposalId, proposalIdTwo}
   107  
   108  		response := otu.GetVotesForAddressAPI(_vote.Addr, proposalIds)
   109  		CheckResponseCode(t, http.StatusOK, response.Code)
   110  
   111  		var votes []models.VoteWithBalance
   112  		json.Unmarshal(response.Body.Bytes(), &votes)
   113  
   114  		for _, v := range votes {
   115  			_expectedWeight := float64(*_vote.PrimaryAccountBalance) * math.Pow(10, -8)
   116  			assert.Equal(t, _expectedWeight, *v.Weight)
   117  		}
   118  	})
   119  }
   120  
   121  /* Balance of NFT */
   122  func TestBalanceOfNFTsStrategy(t *testing.T) {
   123  	clearTable("communities")
   124  	clearTable("community_users")
   125  	clearTable("proposals")
   126  	clearTable("votes")
   127  	clearTable("balances")
   128  	clearTable("nfts")
   129  
   130  	communityId, community := otu.AddCommunitiesWithNFTContract(1, "user1")
   131  	proposalIds, proposals := otu.AddProposalsForStrategy(communityId[0], "balance-of-nfts", 1)
   132  	proposalId := proposalIds[0]
   133  	choices := proposals[0].Choices
   134  
   135  	var contract = &shared.Contract{
   136  		Name:        community.Contract_name,
   137  		Addr:        community.Contract_addr,
   138  		Public_path: community.Public_path,
   139  	}
   140  
   141  	votes, err := otu.GenerateListOfVotesWithNFTs(proposalId, 5, contract)
   142  	if err != nil {
   143  		t.Error(err)
   144  	}
   145  
   146  	otu.AddDummyVotesAndNFTs(votes)
   147  	t.Run("Test Tallying Results For NFT Balance Strategy", func(t *testing.T) {
   148  		strategyName := "balance-of-nfts"
   149  
   150  		s := strategyMap[strategyName]
   151  		s.InitStrategy(otu.A.FlowAdapter, otu.A.DB)
   152  		proposalWithChoices := models.NewProposalResults(proposalId, choices)
   153  		_results, err := s.TallyVotes(votes, proposalWithChoices, proposals[0])
   154  		if err != nil {
   155  			t.Errorf("Error tallying votes: %v", err)
   156  		}
   157  
   158  		response := otu.GetProposalResultsAPI(proposalId)
   159  		CheckResponseCode(t, http.StatusOK, response.Code)
   160  
   161  		var results models.ProposalResults
   162  		json.Unmarshal(response.Body.Bytes(), &results)
   163  
   164  		assert.Equal(t, _results.Results_float["a"], results.Results_float["a"])
   165  	})
   166  
   167  	t.Run("Test Fetching Votes for Proposal", func(t *testing.T) {
   168  		response := otu.GetVotesForProposalAPI(proposalId)
   169  		CheckResponseCode(t, http.StatusOK, response.Code)
   170  
   171  		var body utils.PaginatedResponseWithVotes
   172  		json.Unmarshal(response.Body.Bytes(), &body)
   173  
   174  		// Validate vote weights are returned correctly
   175  		for _, v := range body.Data {
   176  			expectedWeight := float64(1.00)
   177  			assert.Equal(t, expectedWeight, *v.Weight)
   178  		}
   179  	})
   180  
   181  	t.Run("Test Fetching Vote for Address", func(t *testing.T) {
   182  		_vote := (votes)[0]
   183  		response := otu.GetVoteForProposalByAddressAPI(proposalId, _vote.Addr)
   184  
   185  		CheckResponseCode(t, http.StatusOK, response.Code)
   186  
   187  		var vote models.VoteWithBalance
   188  		json.Unmarshal(response.Body.Bytes(), &vote)
   189  
   190  		expectedWeight := float64(1.00)
   191  		assert.Equal(t, expectedWeight, *vote.Weight)
   192  	})
   193  
   194  	// t.Run("Attempt to cheat the NFT strategy", func(t *testing.T) {
   195  	// 	proposalWithChoices := models.NewProposalResults(proposalId, choices)
   196  	// 	_ = otu.TallyResultsForBalanceOfNfts(votes, proposalWithChoices)
   197  
   198  	// 	response := otu.GetProposalResultsAPI(proposalId)
   199  	// 	CheckResponseCode(t, http.StatusOK, response.Code)
   200  
   201  	// 	var correctResults models.ProposalResults
   202  	// 	json.Unmarshal(response.Body.Bytes(), &correctResults)
   203  
   204  	// 	otu.SetupAccountForNFTs("user6")
   205  	// 	otu.TransferNFT("user2", "user3", 1)
   206  
   207  	// 	cheatVote, err := otu.GenerateSingleVoteWithNFT(proposalId, 3, contract)
   208  	// 	if err != nil {
   209  	// 		t.Error(err)
   210  	// 	}
   211  	// 	votesWithCheat := append(*votes, *cheatVote)
   212  	// 	cheatResults := otu.TallyResultsForBalanceOfNfts(&votesWithCheat, proposalWithChoices)
   213  
   214  	// 	response = otu.GetProposalResultsAPI(proposalId)
   215  	// 	CheckResponseCode(t, http.StatusOK, response.Code)
   216  
   217  	// 	json.Unmarshal(response.Body.Bytes(), &cheatResults)
   218  
   219  	// 	//weight should be the same as before the cheat vote was added
   220  	// 	//because the cheat vote should be ignored by the server
   221  	// 	//therefor the cheatResults should be the same as the correctResults
   222  
   223  	// 	assert.Equal(t, correctResults.Proposal_id, cheatResults.Proposal_id)
   224  	// 	assert.Equal(t, correctResults.Results_float["a"], cheatResults.Results_float["a"])
   225  	// 	assert.Equal(t, correctResults.Results_float["b"], cheatResults.Results_float["b"])
   226  	// })
   227  }
   228  
   229  /* Staked Token Weighted Default */
   230  func TestStakedTokenWeightedDefaultStrategy(t *testing.T) {
   231  	clearTable("communities")
   232  	clearTable("community_users")
   233  	clearTable("proposals")
   234  	clearTable("votes")
   235  	clearTable("balances")
   236  
   237  	communityId := otu.AddCommunities(1, "dao")[0]
   238  	proposalIds, proposals := otu.AddProposalsForStrategy(communityId, "staked-token-weighted-default", 2)
   239  	proposalIdTwo := proposalIds[1]
   240  	proposalId := proposalIds[0]
   241  	choices := proposals[0].Choices
   242  	votes := otu.GenerateListOfVotes(proposalId, 10)
   243  	otu.AddDummyVotesAndBalances(votes)
   244  
   245  	t.Run("Test Tallying Results", func(t *testing.T) {
   246  		// Tally results
   247  		strategyName := "staked-token-weighted-default"
   248  
   249  		s := strategyMap[strategyName]
   250  		proposalWithChoices := models.NewProposalResults(proposalId, choices)
   251  		_results, err := s.TallyVotes(votes, proposalWithChoices, proposals[0])
   252  		if err != nil {
   253  			t.Errorf("Error tallying votes: %v", err)
   254  		}
   255  
   256  		// Fetch Proposal Results
   257  		response := otu.GetProposalResultsAPI(proposalId)
   258  		CheckResponseCode(t, http.StatusOK, response.Code)
   259  
   260  		var results models.ProposalResults
   261  		json.Unmarshal(response.Body.Bytes(), &results)
   262  
   263  		assert.Equal(t, _results, results)
   264  	})
   265  
   266  	t.Run("Test Fetching Votes for Proposal", func(t *testing.T) {
   267  		response := otu.GetVotesForProposalAPI(proposalId)
   268  		CheckResponseCode(t, http.StatusOK, response.Code)
   269  
   270  		var body utils.PaginatedResponseWithVotes
   271  		json.Unmarshal(response.Body.Bytes(), &body)
   272  
   273  		// Validate vote weights are returned correctly
   274  		for i, v := range body.Data {
   275  			_vote := (votes)[i]
   276  			expectedWeight := float64(*_vote.StakingBalance) * math.Pow(10, -8)
   277  			assert.Equal(t, expectedWeight, *v.Weight)
   278  		}
   279  	})
   280  
   281  	t.Run("Test Fetching Vote for Address", func(t *testing.T) {
   282  		_vote := (votes)[0]
   283  		response := otu.GetVoteForProposalByAddressAPI(proposalId, _vote.Addr)
   284  
   285  		CheckResponseCode(t, http.StatusOK, response.Code)
   286  
   287  		var vote models.VoteWithBalance
   288  		json.Unmarshal(response.Body.Bytes(), &vote)
   289  
   290  		_expectedWeight := float64(*_vote.StakingBalance) * math.Pow(10, -8)
   291  		assert.Equal(t, _expectedWeight, *vote.Weight)
   292  	})
   293  
   294  	t.Run("Test Fetching Votes For Address", func(t *testing.T) {
   295  		_vote := (votes)[0]
   296  		proposalIds := []int{proposalId, proposalIdTwo}
   297  
   298  		response := otu.GetVotesForAddressAPI(_vote.Addr, proposalIds)
   299  		CheckResponseCode(t, http.StatusOK, response.Code)
   300  
   301  		var votes []models.VoteWithBalance
   302  		json.Unmarshal(response.Body.Bytes(), &votes)
   303  
   304  		for _, v := range votes {
   305  			_expectedWeight := float64(*_vote.StakingBalance) * math.Pow(10, -8)
   306  			assert.Equal(t, _expectedWeight, *v.Weight)
   307  		}
   308  	})
   309  }
   310  
   311  /* One Token One Vote */
   312  // func TestOneTokenOneVoteStrategy(t *testing.T) {
   313  // 	clearTable("communities")
   314  // 	clearTable("community_users")
   315  // 	clearTable("proposals")
   316  // 	clearTable("votes")
   317  // 	clearTable("balances")
   318  
   319  // 	communityId := otu.AddCommunities(1)[0]
   320  // 	proposalIds, proposals := otu.AddProposalsForStrategy(communityId, "one-address-one-vote", 2)
   321  // 	proposalIdTwo := proposalIds[1]
   322  // 	proposalId := proposalIds[0]
   323  // 	choices := proposals[0].Choices
   324  // 	votes := otu.GenerateListOfVotes(proposalId, 10)
   325  // 	otu.AddDummyVotesAndBalances(votes)
   326  
   327  // 	t.Run("Test Tallying Results", func(t *testing.T) {
   328  // 		//Tally Results
   329  // 		proposalWithChoices := models.NewProposalResults(proposalId, choices)
   330  // 		_results := otu.TallyResultsForOneAddressOneVote(votes, proposalWithChoices)
   331  
   332  // 		// Fetch Proposal Results
   333  // 		response := otu.GetProposalResultsAPI(proposalId)
   334  // 		CheckResponseCode(t, http.StatusOK, response.Code)
   335  
   336  // 		var results models.ProposalResults
   337  // 		json.Unmarshal(response.Body.Bytes(), &results)
   338  
   339  // 		assert.Equal(t, _results.Proposal_id, results.Proposal_id)
   340  // 		assert.Equal(t, _results.Results["a"], results.Results["a"])
   341  // 		assert.Equal(t, _results.Results["b"], results.Results["b"])
   342  // 	})
   343  
   344  // 	t.Run("Test Fetching Votes for Proposal", func(t *testing.T) {
   345  // 		response := otu.GetVotesForProposalAPI(proposalId)
   346  
   347  // 		CheckResponseCode(t, http.StatusOK, response.Code)
   348  
   349  // 		var body utils.PaginatedResponseWithVotes
   350  // 		json.Unmarshal(response.Body.Bytes(), &body)
   351  
   352  // 		// Validate vote weights are returned correctly
   353  // 		for _, v := range body.Data {
   354  // 			expectedWeight := float64(1.00)
   355  // 			assert.Equal(t, expectedWeight, *v.Weight)
   356  // 		}
   357  // 	})
   358  
   359  // 	t.Run("Test Fetching Vote for Address", func(t *testing.T) {
   360  // 		_vote := (*votes)[0]
   361  // 		response := otu.GetVoteForProposalByAddressAPI(proposalId, _vote.Addr)
   362  
   363  // 		CheckResponseCode(t, http.StatusOK, response.Code)
   364  
   365  // 		var vote models.VoteWithBalance
   366  // 		json.Unmarshal(response.Body.Bytes(), &vote)
   367  
   368  // 		expectedWeight := float64(1.00)
   369  // 		assert.Equal(t, expectedWeight, *vote.Weight)
   370  // 	})
   371  
   372  // 	t.Run("Test Fetching Votes for Address", func(t *testing.T) {
   373  // 		_vote := (*votes)[0]
   374  // 		proposalIds := []int{proposalId, proposalIdTwo}
   375  // 		response := otu.GetVotesForAddressAPI(_vote.Addr, proposalIds)
   376  
   377  // 		CheckResponseCode(t, http.StatusOK, response.Code)
   378  
   379  // 		var votes []models.VoteWithBalance
   380  // 		json.Unmarshal(response.Body.Bytes(), &votes)
   381  
   382  // 		for _, v := range votes {
   383  // 			expectedWeight := float64(1.00)
   384  // 			assert.Equal(t, expectedWeight, *v.Weight)
   385  // 		}
   386  // 	})
   387  // }