ERC20Votes delegateBySig delagator error

I am testing the ERC20Votes.sol contract from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol" and it seems that if I sign a message with a wrong contract name, the delegator delegating its votes changes as well (it is not the one that originally signed the message).

This is my test code :

import { ethers } from 'hardhat'
import { Contract, ContractFactory } from 'ethers'

async function main() {
    console.log(
        'πŸš€ Starting OpenZeppelin ERC20Votes deployment and interaction...'
    )

    // Get signers
    const [deployer, user1, user2] = await ethers.getSigners()
    console.log('πŸ“ Deployer address:', deployer.address)
    console.log('πŸ‘€ User1 address:', user1.address)
    console.log('πŸ‘€ User2 address:', user2.address)

    // Constants for contract deployment
    const TOKEN_NAME = 'VotingToken'
    const TOKEN_SYMBOL = 'VOTE'

    const DELEGATE_DEADLINE = Math.floor(Date.now() / 1000) + 3600 // 1 hour from now

    try {
        console.log('\nπŸ“¦ Deploying TestERC20Votes contract...')
        const TestERC20VotesFactory: ContractFactory =
            await ethers.getContractFactory('TestERC20Votes')
        const testERC20Votes: Contract = await TestERC20VotesFactory.deploy(
            TOKEN_NAME,
            TOKEN_SYMBOL
        )
        await testERC20Votes.deployed()
        console.log('βœ… TestERC20Votes deployed to:', testERC20Votes.address)

        console.log('\nπŸ—³οΈ Executing delegateBySig on TestERC20Votes...')

        const domain = {
            name: 'WrongName', // WRONG NAME PROVIDED!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
            //name: TOKEN_NAME,
            version: '1',
            chainId: await ethers.provider
                .getNetwork()
                .then((net) => net.chainId),
            verifyingContract: testERC20Votes.address,
        }

        const types = {
            Delegation: [
                { name: 'delegatee', type: 'address' },
                { name: 'nonce', type: 'uint256' },
                { name: 'expiry', type: 'uint256' },
            ],
        }

        const message = {
            delegatee: user2.address,
            nonce: await testERC20Votes.nonces(user1.address),
            expiry: DELEGATE_DEADLINE,
        }

        const signature = await user1._signTypedData(domain, types, message)
        const { v, r, s } = ethers.utils.splitSignature(signature)

        const delegateBySigTx = await testERC20Votes.delegateBySig(
            user2.address,
            message.nonce,
            DELEGATE_DEADLINE,
            v,
            r,
            s
        )
        const response = await delegateBySigTx.wait()

        const delegateChangedEvent = response.events?.find(
            (e: any) => e.event === 'DelegateChanged'
        )

        console.log('βœ… delegateBySig executed successfully')
        console.log('   Delegated from:', user1.address)
        console.log('   Delegated to:', user2.address)

        console.log()
        const ErrorMessage =
            user1.address == delegateChangedEvent.args.delegator
                ? 'βœ… All OK'
                : '❌ WRONG DELEGATOR!!!!!'

        console.log('βœ… delegateBySig event reply')
        console.log(
            '   Delegated from:',
            delegateChangedEvent.args.delegator,
            ' ',
            ErrorMessage
        )
        console.log('   Delegated to:', delegateChangedEvent.args.toDelegate)
    } catch (error) {
        console.error('❌ Error during deployment or execution:', error)
        process.exit(1)
    }
}

// Execute the script
main()
    .then(() => process.exit(0))
    .catch((error) => {
        console.error('❌ Script failed:', error)
        process.exit(1)
    })

And this is its outcome

As you can see the β€œDelegated from” returned by the operation is not the expected one (user 1)

This is the smart contract I used for testing :

// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;

import '@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol';
import '@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol';


contract TestERC20Votes is ERC20Votes {
    constructor(
        string memory name,
        string memory symbol
    ) ERC20Permit(name) ERC20(name, symbol) {}
}

Could it be that "ECDSA.recover" used by "delegateBySign" is not working as expected (returns a random address instead of reverting when provided with a wrong signature)

function delegateBySig(
        address delegatee,
        uint256 nonce,
        uint256 expiry,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public virtual override {
        require(block.timestamp <= expiry, "ERC20Votes: signature expired");
        address signer = ECDSA.recover(
            _hashTypedDataV4(keccak256(abi.encode(_DELEGATION_TYPEHASH, delegatee, nonce, expiry))),
            v,
            r,
            s
        );
        require(nonce == _useNonce(signer), "ERC20Votes: invalid nonce");
        _delegate(signer, delegatee);
    }
1 Like