Developer Guide: Delegation and Voting with EIP-712 Signatures

Hey all, I made a developer guide on applying EIP-712 signatures to Compound protocol governance voting and delegation. There are JS and Solidity examples within. Also use of MetaMask.

3 Likes

very cool! Thank you very much for that!

I am trying to find an example of EIP-712 v4 and how arrays are being encoded.

What I’ve read from the docs is that it should be concatenation of the encoding of each element (as if each element was in a struct with the same name)

I’ve tried to implement it the way I thought it would work but it doesn’t match with what eth-sig-util produces.

Have you worked with arrays ? if not, any idea who has?

1 Like

Hi @adridadou,

Welcome to the community :wave:.

Were you able to find an example? If not, do you want to post in the #support?
I haven’t worked with EIP712 yet.

I haven’t found an example but I found the solution.

To encode an array, you need to use abi.encodePacked(array)
If the array is of a type that needs hashing first, you first need to convert from array to array of hashes and then use abi.encodePacked(array of hashes)

I’m using it in my code so once it’s all ready I can send a link here.

ERC-712 needs some help on the solidity side for sure :slight_smile:

2 Likes

Hi @adam_compound, I have studied your example to build a simple payment channel but I’ve been stuck on the solidity side.

I sign the typed data value with types data structure for domain using the EIP-712 specification by study and following https://docs.ethers.io/v5/api/signer/ and in my solidity code, I followed the method used both on the Metamask blog here https://medium.com/metamask/eip712-is-coming-what-to-expect-and-how-to-use-it-bb92fd1a7a26 and on the Compound Contract medium post you published, although the Compound Contract uses solidity version 0.5 and I am trying to achieve this using 0.7 which has major breaking changes, I have been careful to make appropriate changes.

On Javascript side:

const signPayment = (recipient, amount, nonce, verifyingContract) => {
    const domain = {
        name: "Micro Payment",
        version: "1.0",
        chainId: chainId,
        verifyingContract: verifyingContract,
    };

    const types = {
        Cheque: [
            { name: "recipient", type: "address"},
            { name: "amount", type: "uint256" },
            { name: "nonce", type: "uint256" },
        ]
    };

    const value = {
        recipient: recipient,
        amount: amount,
        nonce: nonce,
    };

    signer._signTypedData(domain, types, value)
        .then((signature) => {
            $('[name=signature]').val(signature);
        })
        .catch((error) => {
            console.log(error);
        });
};

I am on Rinkeby and likewise deployed the solidity code below on Remix to the Rinkeby network.

// SPDX-License-Identifier: GPL-3.0
pragma solidity >0.6.99 <0.8.0;
pragma abicoder v2;

contract ReceiverPays {

address public _verifyingContract;

uint constant chainId = 4;

bytes32 public sigR;

bytes32 public sigS;

uint8  public sigV;

constructor() {
    _verifyingContract = address(this);
}

struct Cheque {
    address recipient;
    uint256 amount;
    uint256 nonce;
    address verifyingContract;
}

/// @notice The EIP-712 typehash for the contract's domain
bytes32 public constant DOMAIN_TYPEHASH = keccak256(abi.encodePacked("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"));

/// @notice The EIP-712 typehash for the delegation struct used by the contract
bytes32 public constant CHEQUE_TYPEHASH = keccak256(abi.encodePacked("Cheque(address recipient,uint256 amount,uint256 nonce,address verifyingContract)"));

function claimPayment(address recipient, uint256 _amount,uint256 _nonce,bytes memory _signature) public returns (address payer) {

    bytes32 domainSeparator = keccak256(
        abi.encode(
            DOMAIN_TYPEHASH,
            keccak256(abi.encodePacked(
                bytes("Micro Payment"),bytes("1.0"),chainId,address(this)
            ))
        )
    );

    bytes32 structHash = keccak256(abi.encode(CHEQUE_TYPEHASH, recipient, _amount, _nonce));
    bytes32 digest = keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));

    (sigR, sigS, sigV) = splitSignature(_signature);

    return ecrecover(digest, sigV, sigR, sigS);
}

function splitSignature(bytes memory sig) public pure returns (bytes32 r, bytes32 s, uint8 v)
{
    require(sig.length == 65, "invalid signature length");

    assembly {
    /*
    First 32 bytes stores the length of the signature

    add(sig, 32) = pointer of sig + 32
    effectively, skips first 32 bytes of signature

    mload(p) loads next 32 bytes starting at the memory address p into memory
    */

    // first 32 bytes, after the length prefix
        r := mload(add(sig, 32))
    // second 32 bytes
        s := mload(add(sig, 64))
    // final byte (first byte of the next 32 bytes)
        v := byte(0, mload(add(sig, 96)))
    }

    return (r, s, v);
}

}

I can’t say for sure where exactly, I am getting this wrong. I would really appreciate all the help I can get! from you.

1 Like

Hi @DreWhyte,

Welcome to the community :wave:

If you are getting stuck with EIP-712 generically (rather than just with Compound) you are welcome to create a new topic to see if anyone in the community can help.

May i know what are you passing as _signature in claimPayment function parameter?

1 Like

To generate the RSV to be passed to the ecrecovery function

2 Likes