@ -2,7 +2,7 @@ const { constants, expectEvent, expectRevert } = require('@openzeppelin/test-hel
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' ) ;
@ -166,7 +166,7 @@ contract('Governor', function (accounts) {
expect ( await web3 . eth . getBalance ( this . receiver . address ) ) . to . be . bignumber . equal ( value ) ;
} ) ;
it ( 'vote with signature' , async function ( ) {
it ( 'votes with signature' , async function ( ) {
const voterBySig = Wallet . generate ( ) ;
const voterBySigAddress = web3 . utils . toChecksumAddress ( voterBySig . getAddressString ( ) ) ;
@ -179,6 +179,8 @@ contract('Governor', function (accounts) {
Ballot : [
{ name : 'proposalId' , type : 'uint256' } ,
{ name : 'support' , type : 'uint8' } ,
{ name : 'voter' , type : 'address' } ,
{ name : 'nonce' , type : 'uint256' } ,
] ,
} ,
domain ,
@ -189,13 +191,19 @@ contract('Governor', function (accounts) {
await this . token . delegate ( voterBySigAddress , { from : voter1 } ) ;
const nonce = await this . mock . nonces ( voterBySigAddress ) ;
// Run proposal
await this . helper . propose ( ) ;
await this . helper . waitForSnapshot ( ) ;
expectEvent ( await this . helper . vote ( { support : Enums . VoteType . For , signature } ) , 'VoteCast' , {
expectEvent (
await this . helper . vote ( { support : Enums . VoteType . For , voter : voterBySigAddress , nonce , signature } ) ,
'VoteCast' ,
{
voter : voterBySigAddress ,
support : Enums . VoteType . For ,
} ) ;
} ,
) ;
await this . helper . waitForDeadline ( ) ;
await this . helper . execute ( ) ;
@ -204,6 +212,7 @@ contract('Governor', function (accounts) {
expect ( await this . mock . hasVoted ( this . proposal . id , voter1 ) ) . to . be . equal ( false ) ;
expect ( await this . mock . hasVoted ( this . proposal . id , voter2 ) ) . to . be . equal ( false ) ;
expect ( await this . mock . hasVoted ( this . proposal . id , voterBySigAddress ) ) . to . be . equal ( true ) ;
expect ( await this . mock . nonces ( voterBySigAddress ) ) . to . be . bignumber . equal ( nonce . addn ( 1 ) ) ;
} ) ;
it ( 'send ethers' , async function ( ) {
@ -297,6 +306,86 @@ contract('Governor', function (accounts) {
} ) ;
} ) ;
describe ( 'on vote by signature' , function ( ) {
beforeEach ( async function ( ) {
this . voterBySig = Wallet . generate ( ) ;
this . voterBySig . address = web3 . utils . toChecksumAddress ( this . voterBySig . getAddressString ( ) ) ;
this . data = ( contract , message ) =>
getDomain ( contract ) . then ( domain => ( {
primaryType : 'Ballot' ,
types : {
EIP712Domain : domainType ( domain ) ,
Ballot : [
{ name : 'proposalId' , type : 'uint256' } ,
{ name : 'support' , type : 'uint8' } ,
{ name : 'voter' , type : 'address' } ,
{ name : 'nonce' , type : 'uint256' } ,
] ,
} ,
domain ,
message ,
} ) ) ;
this . signature = ( contract , message ) =>
this . data ( contract , message )
. then ( data => ethSigUtil . signTypedMessage ( this . voterBySig . getPrivateKey ( ) , { data } ) )
. then ( fromRpcSig ) ;
await this . token . delegate ( this . voterBySig . address , { from : voter1 } ) ;
// Run proposal
await this . helper . propose ( ) ;
await this . helper . waitForSnapshot ( ) ;
} ) ;
it ( '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 ;
} ,
} ;
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 ,
] ) ;
} ) ;
it ( '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 ,
} ;
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 ] ,
) ;
} ) ;
} ) ;
describe ( 'on execute' , function ( ) {
it ( 'if proposal does not exist' , async function ( ) {
await expectRevertCustomError ( this . helper . execute ( ) , 'GovernorNonexistentProposal' , [ this . proposal . id ] ) ;