Skip to content

Add Support for CIP-179 Polls#4152

Open
Cerkoryn wants to merge 2 commits intoIntersectMBO:developfrom
Cerkoryn:poll-metadata-standard
Open

Add Support for CIP-179 Polls#4152
Cerkoryn wants to merge 2 commits intoIntersectMBO:developfrom
Cerkoryn:poll-metadata-standard

Conversation

@Cerkoryn
Copy link
Copy Markdown

@Cerkoryn Cerkoryn commented Mar 14, 2026

List of changes

  • Add support for CIP-179 linked on-chain surveys on governance actions
  • Add backend support to fetch and validate linked survey metadata for a governance action
  • Add backend support to compute survey tally results from survey responses submitted in votes
  • Add survey response metadata support to the governance action voting flow
  • Add frontend support to attach a linked survey when creating a governance action
  • Add frontend support to answer a linked survey while casting a vote
  • Add frontend support to display linked survey details and tally results on the governance action page

Checklist

  • My changes generate no new warnings
  • My code follows the style guidelines of this project
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the changelog
  • I have added tests that prove my fix is effective or that my feature works

Have been having quite a bit of trouble building and running GovTools locally, so I can't validate any of the items on the checklist. I'm hoping that another contributor who can build it might be able to help me out with these changes enough to get it running on one of the testnets at least? If we can get it to that point that I am happy to iterate on it further myself 😅

Right now I was able to verify a basic PoC on the frontend by stitching together various parts, but it's ugly and deviates from the intended development flow. This PR itself should be clean though, I just can't verify it easily 😰

@spannercode
Copy link
Copy Markdown
Contributor

@mesudip Can you review this PR?

@bosko-m bosko-m requested a review from spannercode April 1, 2026 14:50
@mesudip
Copy link
Copy Markdown
Contributor

mesudip commented Apr 2, 2026

I am trying to build the project, The docker image build of backend fails.

Docker build log
31.02 [ 8 of 17] Compiling VVA.Survey       ( src/VVA/Survey.hs, /src/dist-newstyle/build/x86_64-linux/ghc-9.2.7/vva-be-2.0.29/build/VVA/Survey.o, /src/dist-newstyle/build/x86_64-linux/ghc-9.2.7/vva-be-2.0.29/build/VVA/Survey.dyn_o )
31.07 
31.07 src/VVA/Survey.hs:35:62: error:
31.08     • Ambiguous type variable ‘a1’ arising from a use of ‘.=’
31.08       prevents the constraint ‘(aeson-2.1.2.1:Data.Aeson.Types.ToJSON.ToJSON
31.08                                   a1)’ from being solved.
31.08       Probable fix: use a type annotation to specify what ‘a1’ should be.
31.08       These potential instances exist:
31.08         instance aeson-2.1.2.1:Data.Aeson.Types.ToJSON.ToJSON Value
31.08           -- Defined in ‘aeson-2.1.2.1:Data.Aeson.Types.ToJSON’
31.08         instance (aeson-2.1.2.1:Data.Aeson.Types.ToJSON.ToJSON a,
31.08                   aeson-2.1.2.1:Data.Aeson.Types.ToJSON.ToJSON b) =>
31.08                  aeson-2.1.2.1:Data.Aeson.Types.ToJSON.ToJSON (Either a b)
31.08           -- Defined in ‘aeson-2.1.2.1:Data.Aeson.Types.ToJSON’
31.08         instance aeson-2.1.2.1:Data.Aeson.Types.ToJSON.ToJSON Ordering
31.08           -- Defined in ‘aeson-2.1.2.1:Data.Aeson.Types.ToJSON’
31.08         ...plus 27 others
31.08         ...plus 79 instances involving out-of-scope types
31.08         (use -fprint-potential-instances to see them all)
31.08     • In the expression:
31.08         "errors" .= ["No survey link found for this proposal."]
31.08       In the first argument of ‘object’, namely
31.08         ‘["valid" .= False,
31.08           "errors" .= ["No survey link found for this proposal."]]’
31.08       In the second argument of ‘(.=)’, namely
31.08         ‘object
31.08            ["valid" .= False,
31.08             "errors" .= ["No survey link found for this proposal."]]’
31.08    |
31.08 35 |     , "linkValidation" .= object ["valid" .= False, "errors" .= ["No survey link found for this proposal."]]
31.08    |                                                              ^^
31.08 
31.08 src/VVA/Survey.hs:35:66: error:
31.08     • Ambiguous type variable ‘a1’ arising from the literal ‘"No survey link found for this proposal."’
31.08       prevents the constraint ‘(Data.String.IsString
31.08                                   a1)’ from being solved.
31.08       Probable fix: use a type annotation to specify what ‘a1’ should be.
31.08       These potential instances exist:
31.08         instance Data.String.IsString Value
31.08           -- Defined in ‘aeson-2.1.2.1:Data.Aeson.Types.Internal’
31.08         instance Data.String.IsString ByteString
31.08           -- Defined in ‘bytestring-0.11.4.0:Data.ByteString.Internal.Type’
31.08         instance Data.String.IsString SQL.Query
31.09           -- Defined in ‘Database.PostgreSQL.Simple.Types’
31.09         ...plus two others
31.09         ...plus 34 instances involving out-of-scope types
31.09         (use -fprint-potential-instances to see them all)
31.09     • In the expression: "No survey link found for this proposal."
31.09       In the second argument of ‘(.=)’, namely
31.09         ‘["No survey link found for this proposal."]’
31.09       In the expression:
31.09         "errors" .= ["No survey link found for this proposal."]
31.09    |
31.09 35 |     , "linkValidation" .= object ["valid" .= False, "errors" .= ["No survey link found for this proposal."]]
31.09    |                                                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
31.09 
31.09 src/VVA/Survey.hs:65:16: error:
31.09     • Ambiguous type variable ‘a0’ arising from a use of ‘.=’
31.09       prevents the constraint ‘(aeson-2.1.2.1:Data.Aeson.Types.ToJSON.ToJSON
31.09                                   a0)’ from being solved.
31.09       Probable fix: use a type annotation to specify what ‘a0’ should be.
31.09       These potential instances exist:
31.09         instance aeson-2.1.2.1:Data.Aeson.Types.ToJSON.ToJSON Value
31.09           -- Defined in ‘aeson-2.1.2.1:Data.Aeson.Types.ToJSON’
31.09         instance (aeson-2.1.2.1:Data.Aeson.Types.ToJSON.ToJSON a,
31.09                   aeson-2.1.2.1:Data.Aeson.Types.ToJSON.ToJSON b) =>
31.09                  aeson-2.1.2.1:Data.Aeson.Types.ToJSON.ToJSON (Either a b)
31.09           -- Defined in ‘aeson-2.1.2.1:Data.Aeson.Types.ToJSON’
31.09         instance aeson-2.1.2.1:Data.Aeson.Types.ToJSON.ToJSON Ordering
31.09           -- Defined in ‘aeson-2.1.2.1:Data.Aeson.Types.ToJSON’
31.09         ...plus 27 others
31.09         ...plus 79 instances involving out-of-scope types
31.09         (use -fprint-potential-instances to see them all)
31.09     • In the expression:
31.09         "errors" .= ["No survey tally available for this proposal."]
31.09       In the first argument of ‘object’, namely
31.09         ‘["surveyTxId" .= (Nothing :: Maybe Text),
31.09           "totals"
31.09             .=
31.09               object
31.09                 ["totalSeen" .= (0 :: Integer), "valid" .= (0 :: Integer), ....],
31.09           "roleResults" .= [object [...]],
31.09           "errors" .= ["No survey tally available for this proposal."]]’
31.09       In the expression:
31.09         object
31.09           ["surveyTxId" .= (Nothing :: Maybe Text),
31.09            "totals"
31.09              .=
31.09                object
31.09                  ["totalSeen" .= (0 :: Integer), "valid" .= (0 :: Integer), ....],
31.09            "roleResults" .= [object [...]],
31.09            "errors" .= ["No survey tally available for this proposal."]]
31.09    |
31.09 65 |     , "errors" .= ["No survey tally available for this proposal."]
31.09    |                ^^
31.09 
31.09 src/VVA/Survey.hs:65:20: error:
31.09     • Ambiguous type variable ‘a0’ arising from the literal ‘"No survey tally available for this proposal."’
31.09       prevents the constraint ‘(Data.String.IsString
31.09                                   a0)’ from being solved.
31.09       Probable fix: use a type annotation to specify what ‘a0’ should be.
31.09       These potential instances exist:
31.09         instance Data.String.IsString Value
31.09           -- Defined in ‘aeson-2.1.2.1:Data.Aeson.Types.Internal’
31.09         instance Data.String.IsString ByteString
31.09           -- Defined in ‘bytestring-0.11.4.0:Data.ByteString.Internal.Type’
31.09         instance Data.String.IsString SQL.Query
31.09           -- Defined in ‘Database.PostgreSQL.Simple.Types’
31.09         ...plus two others
31.09         ...plus 34 instances involving out-of-scope types
31.09         (use -fprint-potential-instances to see them all)
31.09     • In the expression: "No survey tally available for this proposal."
31.09       In the second argument of ‘(.=)’, namely
31.09         ‘["No survey tally available for this proposal."]’
31.09       In the expression:
31.09         "errors" .= ["No survey tally available for this proposal."]
31.09    |
31.09 65 |     , "errors" .= ["No survey tally available for this proposal."]
31.09    |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
31.09 
31.09 src/VVA/Survey.hs:74:13: error:
31.11     • Couldn't match type ‘m’ with ‘IO’
31.11       Expected: m [SQL.Only Value]
31.11         Actual: IO [SQL.Only Value]
31.11       ‘m’ is a rigid type variable bound by
31.11         the type signature for:
31.11           getProposalSurvey :: forall r (m :: * -> *).
31.11                                (Has ConnectionPool r, Has VVAConfig r, MonadReader r m,
31.11                                 MonadIO m) =>
31.11                                Text -> Integer -> m Value
31.11         at src/VVA/Survey.hs:(68,1)-(72,9)
31.11     • In a stmt of a 'do' block:
31.11         result <- liftIO
31.11                     $ SQL.query conn getProposalSurveySql (txHash, index) ::
31.11                     IO [SQL.Only Value]
31.11       In the expression:
31.11         do result <- liftIO
31.11                        $ SQL.query conn getProposalSurveySql (txHash, index) ::
31.11                        IO [SQL.Only Value]
31.11            case result of
31.12              [SQL.Only payload] -> pure payload
31.12              _ -> pure emptySurveyPayload
31.12       In the second argument of ‘($)’, namely
31.12         ‘\ conn
31.12            -> do result <- liftIO $ SQL.query conn getProposalSurveySql ... ::
31.12                              IO [SQL.Only Value]
31.12                  case result of
31.12                    [SQL.Only payload] -> ...
31.12                    _ -> ...’
31.12     • Relevant bindings include
31.12         getProposalSurvey :: Text -> Integer -> m Value
31.13           (bound at src/VVA/Survey.hs:73:1)
31.13    |
31.13 74 |   result <- liftIO $
31.13    |             ^^^^^^^^...
31.13 
31.13 src/VVA/Survey.hs:87:13: error:
31.13     • Couldn't match type ‘m’ with ‘IO’
31.13       Expected: m [SQL.Only Value]
31.13         Actual: IO [SQL.Only Value]
31.13       ‘m’ is a rigid type variable bound by
31.13         the type signature for:
31.13           getProposalSurveyTally :: forall r (m :: * -> *).
31.13                                     (Has ConnectionPool r, Has VVAConfig r, MonadReader r m,
31.13                                      MonadIO m) =>
31.13                                     Text -> Integer -> Text -> m Value
31.13         at src/VVA/Survey.hs:(80,1)-(85,9)
31.13     • In a stmt of a 'do' block:
31.13         result <- liftIO
31.13                     $ SQL.query
31.13                         conn getProposalSurveyTallySql
31.13                         (txHash, index, weighting, weighting) ::
31.13                     IO [SQL.Only Value]
31.13       In the expression:
31.13         do result <- liftIO
31.13                        $ SQL.query
31.13                            conn getProposalSurveyTallySql
31.13                            (txHash, index, weighting, weighting) ::
31.13                        IO [SQL.Only Value]
31.13            case result of
31.13              [SQL.Only payload] -> pure payload
31.13              _ -> pure (emptySurveyTallyPayload weighting)
31.13       In the second argument of ‘($)’, namely
31.13         ‘\ conn
31.13            -> do result <- liftIO
31.13                              $ SQL.query conn getProposalSurveyTallySql ... ::
31.13                              IO [SQL.Only Value]
31.13                  case result of
31.13                    [SQL.Only payload] -> ...
31.13                    _ -> ...’
31.13     • Relevant bindings include
31.13         getProposalSurveyTally :: Text -> Integer -> Text -> m Value
31.13           (bound at src/VVA/Survey.hs:86:1)
31.13    |
31.13 87 |   result <- liftIO $
31.13    |             ^^^^^^^^...
31.60 Error: cabal: Failed to build vva-be-2.0.29 (which is required by exe:vva-be
31.60 from vva-be-2.0.29).
31.60 
------
Dockerfile:6
--------------------
   4 |     WORKDIR /src
   5 |     COPY . .
   6 | >>> RUN cabal build
   7 |     RUN cp dist-newstyle/build/x86_64-linux/ghc-9.2.7/vva-be-2.0.29/x/vva-be/build/vva-be/vva-be /usr/local/bin
   8 |     
--------------------

@mesudip
Copy link
Copy Markdown
Contributor

mesudip commented Apr 2, 2026

@Cerkoryn , I managed to get the build working with some modification, and it is deployed. Can you verify that this is how you wanted to implement the feature?

cc: @bosko-m

@mesudip
Copy link
Copy Markdown
Contributor

mesudip commented Apr 2, 2026

PR Assessment

The PR is not ready for merging in current state.

CIP / behavior

This is not full CIP-0179 compliance.

Main issues:

  • Link validation is incomplete. The backend checks kind and surveyTxId, but does not validate specVersion.
  • surveyDetails validation is shallow. It does not fully validate question structure, duplicate questionIds, or the full survey schema.
  • Eligible responder roles are hardcoded incorrectly for some action types.
  • Survey response validation is too weak. Invalid responses can still be counted.
  • Tallying is not draft/reference-compatible. It is DRep-only and the output shape is much simpler than what the frontend expects.

Overall: the PR introduces the shape of the feature, but not a compliant or reliable implementation.

Code / implementation

There are also concrete implementation problems:

  • The frontend PR branch does not typecheck: index.ts:43 exports ./survey, but that file does not exist.
  • The backend tally response does not match what the frontend renders, so tallies will not display properly.
  • Tallying is hardcoded to DRep responses only, even though the feature model suggests broader role support.
  • Validation is too shallow for the tally output to be trusted.
  • Lint also fails in touched frontend files.

@Cerkoryn
Copy link
Copy Markdown
Author

Cerkoryn commented Apr 2, 2026

@mesudip I greatly appreciate your help. I had a lot of difficulties trying to get all the dependencies to run to build and test GovTool locally with this feature, so I'm not surprised that I missed a bunch of things. Sincerest apologies for that 🙏

I will take a look at that preview page you set up (which is enormously helpful BTW) as soon as I am able to, which is likely after spring break. Then I will address each of those errors and submit an update to this PR so that we can get it implemented in a clean and robust way.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants