@ -2,11 +2,12 @@ const { expectEvent } = require('@openzeppelin/test-helpers');
const { expect } = require ( 'chai' ) ;
const ethSigUtil = require ( 'eth-sig-util' ) ;
const Wallet = require ( 'ethereumjs-wallet' ) . default ;
const { fromRpcSig } = require ( 'ethereumjs-util' ) ;
const { fromRpcSig , toRpcSig } = require ( 'ethereumjs-util' ) ;
const Enums = require ( '../../helpers/enums' ) ;
const { getDomain , domainType } = require ( '../../helpers/eip712' ) ;
const { GovernorHelper } = require ( '../../helpers/governance' ) ;
const { expectRevertCustomError } = require ( '../../helpers/customError' ) ;
const Governor = artifacts . require ( '$GovernorWithParamsMock' ) ;
const CallReceiver = artifacts . require ( 'CallReceiverMock' ) ;
@ -119,56 +120,119 @@ contract('GovernorWithParams', function (accounts) {
expect ( votes . forVotes ) . to . be . bignumber . equal ( weight ) ;
} ) ;
it ( 'Voting with params by signature is properly supported' , async function ( ) {
const voterBySig = Wallet . generate ( ) ;
const voterBySigAddress = web3 . utils . toChecksumAddress ( voterBySig . getAddressString ( ) ) ;
describe ( 'voting by signature' , function ( ) {
beforeEach ( async function ( ) {
this . voterBySig = Wallet . generate ( ) ;
this . voterBySig . address = web3 . utils . toChecksumAddress ( this . voterBySig . getAddressString ( ) ) ;
const signature = ( contract , message ) =>
getDomain ( contract )
. then ( domain => ( {
this . data = ( contract , message ) =>
getDomain ( contract ) . then ( domain => ( {
primaryType : 'ExtendedBallot' ,
types : {
EIP712Domain : domainType ( domain ) ,
ExtendedBallot : [
{ name : 'proposalId' , type : 'uint256' } ,
{ name : 'support' , type : 'uint8' } ,
{ name : 'voter' , type : 'address' } ,
{ name : 'nonce' , type : 'uint256' } ,
{ name : 'reason' , type : 'string' } ,
{ name : 'params' , type : 'bytes' } ,
] ,
} ,
domain ,
message ,
} ) )
. then ( data => ethSigUtil . signTypedMessage ( voterBySig . getPrivateKey ( ) , { data } ) )
. then ( fromRpcSig ) ;
} ) ) ;
await this . token . delegate ( voterBySigAddress , { from : voter2 } ) ;
this . signature = ( contract , message ) =>
this . data ( contract , message )
. then ( data => ethSigUtil . signTypedMessage ( this . voterBySig . getPrivateKey ( ) , { data } ) )
. then ( fromRpcSig ) ;
// Run proposal
await this . helper . propose ( ) ;
await this . helper . waitForSnapshot ( ) ;
await this . token . delegate ( this . voterBySig . address , { from : voter2 } ) ;
const weight = web3 . utils . toBN ( web3 . utils . toWei ( '7' ) ) . sub ( rawParams . uintParam ) ;
// Run proposal
await this . helper . propose ( ) ;
await this . helper . waitForSnapshot ( ) ;
} ) ;
const tx = await this . helper . vote ( {
support : Enums . VoteType . For ,
reason : 'no particular reason' ,
params : encodedParams ,
signature ,
it ( 'is properly supported' , async function ( ) {
const weight = web3 . utils . toBN ( web3 . utils . toWei ( '7' ) ) . sub ( rawParams . uintParam ) ;
const nonce = await this . mock . nonces ( this . voterBySig . address ) ;
const tx = await this . helper . vote ( {
support : Enums . VoteType . For ,
voter : this . voterBySig . address ,
nonce ,
reason : 'no particular reason' ,
params : encodedParams ,
signature : this . signature ,
} ) ;
expectEvent ( tx , 'CountParams' , { ... rawParams } ) ;
expectEvent ( tx , 'VoteCastWithParams' , {
voter : this . voterBySig . address ,
proposalId : this . proposal . id ,
support : Enums . VoteType . For ,
weight ,
reason : 'no particular reason' ,
params : encodedParams ,
} ) ;
const votes = await this . mock . proposalVotes ( this . proposal . id ) ;
expect ( votes . forVotes ) . to . be . bignumber . equal ( weight ) ;
expect ( await this . mock . nonces ( this . voterBySig . address ) ) . to . be . bignumber . equal ( nonce . addn ( 1 ) ) ;
} ) ;
expectEvent ( tx , 'CountParams' , { ... rawParams } ) ;
expectEvent ( tx , 'VoteCastWithParams' , {
voter : voterBySigAddress ,
proposalId : this . proposal . id ,
support : Enums . VoteType . For ,
weight ,
reason : 'no particular reason' ,
params : encodedParams ,
it ( 'reverts if signature does not match signer' , async function ( ) {
const nonce = await this . mock . nonces ( this . voterBySig . address ) ;
const voteParams = {
support : Enums . VoteType . For ,
voter : this . voterBySig . address ,
nonce ,
signature : async ( ... params ) => {
const sig = await this . signature ( ... params ) ;
sig . s [ 12 ] ^= 0xff ;
return sig ;
} ,
reason : 'no particular reason' ,
params : encodedParams ,
} ;
const { r , s , v } = await this . helper . sign ( voteParams ) ;
const message = this . helper . forgeMessage ( voteParams ) ;
const data = await this . data ( this . mock , message ) ;
await expectRevertCustomError ( this . helper . vote ( voteParams ) , 'GovernorInvalidSigner' , [
ethSigUtil . recoverTypedSignature ( { sig : toRpcSig ( v , r , s ) , data } ) ,
voteParams . voter ,
] ) ;
} ) ;
const votes = await this . mock . proposalVotes ( this . proposal . id ) ;
expect ( votes . forVotes ) . to . be . bignumber . equal ( weight ) ;
it ( 'reverts if vote nonce is incorrect' , async function ( ) {
const nonce = await this . mock . nonces ( this . voterBySig . address ) ;
const voteParams = {
support : Enums . VoteType . For ,
voter : this . voterBySig . address ,
nonce : nonce . addn ( 1 ) ,
signature : this . signature ,
reason : 'no particular reason' ,
params : encodedParams ,
} ;
const { r , s , v } = await this . helper . sign ( voteParams ) ;
const message = this . helper . forgeMessage ( voteParams ) ;
const data = await this . data ( this . mock , { ... message , nonce } ) ;
await expectRevertCustomError (
this . helper . vote ( voteParams ) ,
// The signature check implies the nonce can't be tampered without changing the signer
'GovernorInvalidSigner' ,
[ ethSigUtil . recoverTypedSignature ( { sig : toRpcSig ( v , r , s ) , data } ) , voteParams . voter ] ,
) ;
} ) ;
} ) ;
} ) ;
}