diff --git a/contracts/libraries/helpers/MetaTxHelpers.sol b/contracts/libraries/helpers/MetaTxHelpers.sol index b19e903..c65ddce 100644 --- a/contracts/libraries/helpers/MetaTxHelpers.sol +++ b/contracts/libraries/helpers/MetaTxHelpers.sol @@ -6,6 +6,8 @@ import {DataTypes} from '../DataTypes.sol'; import {Errors} from '../Errors.sol'; import {DataTypes} from '../DataTypes.sol'; import {GeneralHelpers} from './GeneralHelpers.sol'; +import 'hardhat/console.sol'; + import '../Constants.sol'; /** @@ -38,15 +40,14 @@ library MetaTxHelpers { ) internal { if (spender == address(0)) revert Errors.ZeroSpender(); address owner = GeneralHelpers.unsafeOwnerOf(tokenId); - _validateRecoveredAddress( - _calculateDigest( - keccak256( - abi.encode(PERMIT_TYPEHASH, spender, tokenId, _sigNonces(owner), sig.deadline) - ) - ), - owner, - sig + bytes32 digest = _calculateDigest( + keccak256( + abi.encode(PERMIT_TYPEHASH, spender, tokenId, _sigNonces(owner), sig.deadline) + ) ); + console.log('On-Chain digest is:'); + console.logBytes32(digest); + _validateRecoveredAddress(digest, owner, sig); emit Approval(owner, spender, tokenId); } @@ -317,6 +318,7 @@ library MetaTxHelpers { /** * @dev Wrapper for ecrecover to reduce code size, used in meta-tx specific functions. + * todo: Add error. */ function _validateRecoveredAddress( bytes32 digest, @@ -326,12 +328,13 @@ library MetaTxHelpers { if (sig.deadline < block.timestamp) revert Errors.SignatureExpired(); if (expectedAddress.code.length != 0) { + console.log("Calling EIP1271Implementer"); if ( IEIP1271Implementer(expectedAddress).isValidSignature( digest, - abi.encodePacked(sig.r, sig.s, sig.v) + abi.encode(sig.r, sig.s, sig.v) ) != EIP1271_MAGIC_VALUE - ) revert(); + ) revert('1271 Recovery failed'); } else { address recoveredAddress = ecrecover(digest, sig.v, sig.r, sig.s); if (recoveredAddress == address(0) || recoveredAddress != expectedAddress) diff --git a/contracts/misc/LensPeriphery.sol b/contracts/misc/LensPeriphery.sol index 0166c21..1f96c10 100644 --- a/contracts/misc/LensPeriphery.sol +++ b/contracts/misc/LensPeriphery.sol @@ -170,7 +170,7 @@ contract LensPeriphery { /** * @dev Wrapper for ecrecover to reduce code size, used in meta-tx specific functions. * - * @notice In order to use the GeneralHelpers here, we will need to re-deploy. + * @notice In order to use the MetaTXHelpers here, we will need to re-deploy. */ function _validateRecoveredAddress( bytes32 digest, diff --git a/contracts/mocks/MockEIP1271Implementer.sol b/contracts/mocks/MockEIP1271Implementer.sol new file mode 100644 index 0000000..e36df47 --- /dev/null +++ b/contracts/mocks/MockEIP1271Implementer.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.15; + +import 'hardhat/console.sol'; +import {IEIP1271Implementer} from '../interfaces/IEIP1271Implementer.sol'; + +// todo: should receive 65 length bytes and decode manually. +contract MockEIP1271Implementer is IEIP1271Implementer { + // bytes4(keccak256("isValidSignature(bytes32,bytes)") + bytes4 internal constant MAGIC_VALUE = 0x1626ba7e; + + address public immutable OWNER; + + constructor() { + OWNER = msg.sender; + } + + function isValidSignature(bytes32 _hash, bytes memory _signature) + external + view + override + returns (bytes4) + { + (bytes32 r, bytes32 s, uint8 v) = abi.decode(_signature, (bytes32, bytes32, uint8)); + console.log('Decoded r:'); + console.logBytes32(r); + console.log('Decoded s:'); + console.logBytes32(s); + console.log('Decoded v:'); + console.log(v); + console.log('ON CHAIN HASHED MESSAGE:'); + console.logBytes32(keccak256(abi.encodePacked('\x19Ethereum Signed Message:\n32', _hash))); + address signer = ecrecover( + keccak256(abi.encodePacked('\x19Ethereum Signed Message:\n32', _hash)), + v, + r, + s + ); + console.log('On-chain recovered signer:', signer); + console.log('On-chain owner:', OWNER); + require(signer != address(0), 'Invalid recovery'); + return signer == OWNER ? MAGIC_VALUE : bytes4(0xFFFFFFFF); + } +} diff --git a/test/helpers/utils.ts b/test/helpers/utils.ts index ca4b273..5d1ff27 100644 --- a/test/helpers/utils.ts +++ b/test/helpers/utils.ts @@ -12,7 +12,16 @@ import { } from '../__setup.spec'; import { expect } from 'chai'; import { HARDHAT_CHAINID, MAX_UINT256 } from './constants'; -import { BytesLike, hexlify, keccak256, RLP, toUtf8Bytes } from 'ethers/lib/utils'; +import { + BytesLike, + concat, + hashMessage, + hexlify, + keccak256, + RLP, + toUtf8Bytes, + _TypedDataEncoder, +} from 'ethers/lib/utils'; import { LensHub__factory } from '../../typechain-types'; import { TransactionReceipt, TransactionResponse } from '@ethersproject/providers'; import hre, { ethers } from 'hardhat'; @@ -29,6 +38,7 @@ import { PostDataStruct, PostWithSigDataStruct, } from '../../typechain-types/LensHub'; +import { messagePrefix } from 'ethers/node_modules/@ethersproject/hash'; export enum ProtocolState { Unpaused, @@ -247,6 +257,18 @@ export async function getPermitParts( return await getSig(msgParams); } +export async function getPermitMessageParts( + nft: string, + name: string, + spender: string, + tokenId: BigNumberish, + nonce: number, + deadline: string +): Promise<{ v: number; r: string; s: string }> { + const msgParams = buildPermitParams(nft, name, spender, tokenId, nonce, deadline); + return await getMessageSig(msgParams); +} + export async function getPermitForAllParts( nft: string, name: string, @@ -1107,6 +1129,19 @@ async function getSig(msgParams: { return utils.splitSignature(sig); } +async function getMessageSig(msgParams: { + domain: any; + types: any; + value: any; +}): Promise<{ v: number; r: string; s: string }> { + const digest = _TypedDataEncoder.hash(msgParams.domain, msgParams.types, msgParams.value); + console.log('Script-generated digest:'); + console.log(digest); + console.log('SCRIPT COMPUTED MESSAGE:', hashMessage(digest)); + const sig = await testWallet.signMessage(digest); + return utils.splitSignature(sig); +} + function domain(): { name: string; version: string; chainId: number; verifyingContract: string } { return { name: LENS_HUB_NFT_NAME, diff --git a/test/nft/lens-nft-base.spec.ts b/test/nft/lens-nft-base.spec.ts index 9943ffe..fc97036 100644 --- a/test/nft/lens-nft-base.spec.ts +++ b/test/nft/lens-nft-base.spec.ts @@ -9,6 +9,7 @@ import { getBurnWithSigparts, getChainId, getPermitForAllParts, + getPermitMessageParts, getPermitParts, } from '../helpers/utils'; import { @@ -25,6 +26,7 @@ import { userAddress, } from '../__setup.spec'; import { hardhatArguments } from 'hardhat'; +import { MockEIP1271Implementer__factory } from '../../typechain-types'; makeSuiteCleanRoom('Lens NFT Base Functionality', function () { context('generic', function () { @@ -486,6 +488,48 @@ makeSuiteCleanRoom('Lens NFT Base Functionality', function () { lensHub.connect(user).burnWithSig(FIRST_PROFILE_ID, { v, r, s, deadline: MAX_UINT256 }) ).to.not.be.reverted; }); + + it.only('TestWallet should deploy EIP1271 implementer, transfer NFT to it, sign message and permit user, user should transfer NFT, send back NFT and fail to transfer it again', async function () { + const sigContract = await new MockEIP1271Implementer__factory(testWallet).deploy(); + const nonce = (await lensHub.sigNonces(sigContract.address)).toNumber(); + await expect( + lensHub + .connect(testWallet) + .transferFrom(testWallet.address, sigContract.address, FIRST_PROFILE_ID) + ).to.not.be.reverted; + + console.log('here'); + const { v, r, s } = await getPermitMessageParts( + lensHub.address, + LENS_HUB_NFT_NAME, + userAddress, + FIRST_PROFILE_ID, + nonce, + MAX_UINT256 + ); + + console.log('TestWallet addr:', testWallet.address); + console.log('Script r:'); + console.log(r); + console.log('Script s:'); + console.log(s); + console.log('Script v:'); + console.log(v); + + await expect( + lensHub.permit( + userAddress, + FIRST_PROFILE_ID, + { + v, + r, + s, + deadline: MAX_UINT256, + }, + { gasLimit: 12450000 } + ) + ).to.be.revertedWith('unknown'); + }); }); }); });