feat: (WIP) Refactored meta transactions into a library. Includes hardhat-tracer.

This commit is contained in:
Peter Michael
2022-05-26 17:39:11 -04:00
parent 9f3b62aab1
commit 6cfaa580b3
21 changed files with 2200 additions and 4569 deletions

View File

@@ -3,6 +3,7 @@
pragma solidity 0.8.10;
import {ILensHub} from '../interfaces/ILensHub.sol';
import {Events} from '../libraries/Events.sol';
import {Helpers} from '../libraries/Helpers.sol';
import {Constants} from '../libraries/Constants.sol';
@@ -11,7 +12,9 @@ import {Errors} from '../libraries/Errors.sol';
import {PublishingLogic} from '../libraries/PublishingLogic.sol';
import {ProfileTokenURILogic} from '../libraries/ProfileTokenURILogic.sol';
import {InteractionLogic} from '../libraries/InteractionLogic.sol';
import {LensNFTBase} from './base/LensNFTBase.sol';
import {MetaTxLib} from '../libraries/MetaTxLib.sol';
import {LensHubNFTBase} from './base/LensHubNFTBase.sol';
import {LensMultiState} from './base/LensMultiState.sol';
import {LensHubStorage} from './storage/LensHubStorage.sol';
import {VersionedInitializable} from '../upgradeability/VersionedInitializable.sol';
@@ -29,7 +32,13 @@ import {IERC721Enumerable} from '@openzeppelin/contracts/token/ERC721/extensions
* 1. Both Follow & Collect NFTs invoke an LensHub callback on transfer with the sole purpose of emitting an event.
* 2. Almost every event in the protocol emits the current block timestamp, reducing the need to fetch it manually.
*/
contract LensHub is LensNFTBase, VersionedInitializable, LensMultiState, LensHubStorage, ILensHub {
contract LensHub is
LensHubNFTBase,
VersionedInitializable,
LensMultiState,
LensHubStorage,
ILensHub
{
uint256 internal constant REVISION = 1;
address internal immutable FOLLOW_NFT_IMPL;
@@ -140,6 +149,27 @@ contract LensHub is LensNFTBase, VersionedInitializable, LensMultiState, LensHub
/// *****PROFILE OWNER FUNCTIONS*****
/// *********************************
function permit(
address spender,
uint256 tokenId,
DataTypes.EIP712Signature calldata sig
) external {
MetaTxLib.permit(spender, tokenId, sig);
}
function permitForAll(
address owner,
address operator,
bool approved,
DataTypes.EIP712Signature calldata sig
) external {
MetaTxLib.permitForAll(owner, operator, approved, sig);
}
function getDomainSeparator() external view returns (bytes32) {
return MetaTxLib.getDomainSeparator();
}
/// @inheritdoc ILensHub
function createProfile(DataTypes.CreateProfileData calldata vars)
external
@@ -173,24 +203,8 @@ contract LensHub is LensNFTBase, VersionedInitializable, LensMultiState, LensHub
override
whenNotPaused
{
unchecked {
_validateRecoveredAddress(
_calculateDigest(
keccak256(
abi.encode(
SET_DEFAULT_PROFILE_WITH_SIG_TYPEHASH,
vars.wallet,
vars.profileId,
sigNonces[vars.wallet]++,
vars.sig.deadline
)
)
),
vars.wallet,
vars.sig
);
_setDefaultProfile(vars.wallet, vars.profileId);
}
MetaTxLib.baseSetDefaultProfileWithSig(vars);
_setDefaultProfile(vars.wallet, vars.profileId);
}
/// @inheritdoc ILensHub
@@ -215,25 +229,7 @@ contract LensHub is LensNFTBase, VersionedInitializable, LensMultiState, LensHub
override
whenNotPaused
{
address owner = ownerOf(vars.profileId);
unchecked {
_validateRecoveredAddress(
_calculateDigest(
keccak256(
abi.encode(
SET_FOLLOW_MODULE_WITH_SIG_TYPEHASH,
vars.profileId,
vars.followModule,
keccak256(vars.followModuleInitData),
sigNonces[owner]++,
vars.sig.deadline
)
)
),
owner,
vars.sig
);
}
MetaTxLib.baseSetFollowModuleWithSig(vars);
PublishingLogic.setFollowModule(
vars.profileId,
vars.followModule,
@@ -255,24 +251,7 @@ contract LensHub is LensNFTBase, VersionedInitializable, LensMultiState, LensHub
override
whenNotPaused
{
address owner = ownerOf(vars.profileId);
unchecked {
_validateRecoveredAddress(
_calculateDigest(
keccak256(
abi.encode(
SET_DISPATCHER_WITH_SIG_TYPEHASH,
vars.profileId,
vars.dispatcher,
sigNonces[owner]++,
vars.sig.deadline
)
)
),
owner,
vars.sig
);
}
MetaTxLib.baseSetDispatcherWithSig(vars);
_setDispatcher(vars.profileId, vars.dispatcher);
}
@@ -292,24 +271,7 @@ contract LensHub is LensNFTBase, VersionedInitializable, LensMultiState, LensHub
override
whenNotPaused
{
address owner = ownerOf(vars.profileId);
unchecked {
_validateRecoveredAddress(
_calculateDigest(
keccak256(
abi.encode(
SET_PROFILE_IMAGE_URI_WITH_SIG_TYPEHASH,
vars.profileId,
keccak256(bytes(vars.imageURI)),
sigNonces[owner]++,
vars.sig.deadline
)
)
),
owner,
vars.sig
);
}
MetaTxLib.baseSetProfileImageURIWithSig(vars);
_setProfileImageURI(vars.profileId, vars.imageURI);
}
@@ -329,24 +291,7 @@ contract LensHub is LensNFTBase, VersionedInitializable, LensMultiState, LensHub
override
whenNotPaused
{
address owner = ownerOf(vars.profileId);
unchecked {
_validateRecoveredAddress(
_calculateDigest(
keccak256(
abi.encode(
SET_FOLLOW_NFT_URI_WITH_SIG_TYPEHASH,
vars.profileId,
keccak256(bytes(vars.followNFTURI)),
sigNonces[owner]++,
vars.sig.deadline
)
)
),
owner,
vars.sig
);
}
MetaTxLib.baseSetFollowNFTURIWithSig(vars);
_setFollowNFTURI(vars.profileId, vars.followNFTURI);
}
@@ -376,28 +321,7 @@ contract LensHub is LensNFTBase, VersionedInitializable, LensMultiState, LensHub
whenPublishingEnabled
returns (uint256)
{
address owner = ownerOf(vars.profileId);
unchecked {
_validateRecoveredAddress(
_calculateDigest(
keccak256(
abi.encode(
POST_WITH_SIG_TYPEHASH,
vars.profileId,
keccak256(bytes(vars.contentURI)),
vars.collectModule,
keccak256(vars.collectModuleInitData),
vars.referenceModule,
keccak256(vars.referenceModuleInitData),
sigNonces[owner]++,
vars.sig.deadline
)
)
),
owner,
vars.sig
);
}
MetaTxLib.basePostWithSig(vars);
return
_createPost(
vars.profileId,
@@ -427,31 +351,7 @@ contract LensHub is LensNFTBase, VersionedInitializable, LensMultiState, LensHub
whenPublishingEnabled
returns (uint256)
{
address owner = ownerOf(vars.profileId);
unchecked {
_validateRecoveredAddress(
_calculateDigest(
keccak256(
abi.encode(
COMMENT_WITH_SIG_TYPEHASH,
vars.profileId,
keccak256(bytes(vars.contentURI)),
vars.profileIdPointed,
vars.pubIdPointed,
keccak256(vars.referenceModuleData),
vars.collectModule,
keccak256(vars.collectModuleInitData),
vars.referenceModule,
keccak256(vars.referenceModuleInitData),
sigNonces[owner]++,
vars.sig.deadline
)
)
),
owner,
vars.sig
);
}
MetaTxLib.baseCommentWithSig(vars);
return
_createComment(
DataTypes.CommentData(
@@ -486,28 +386,7 @@ contract LensHub is LensNFTBase, VersionedInitializable, LensMultiState, LensHub
whenPublishingEnabled
returns (uint256)
{
address owner = ownerOf(vars.profileId);
unchecked {
_validateRecoveredAddress(
_calculateDigest(
keccak256(
abi.encode(
MIRROR_WITH_SIG_TYPEHASH,
vars.profileId,
vars.profileIdPointed,
vars.pubIdPointed,
keccak256(vars.referenceModuleData),
vars.referenceModule,
keccak256(vars.referenceModuleInitData),
sigNonces[owner]++,
vars.sig.deadline
)
)
),
owner,
vars.sig
);
}
MetaTxLib.baseMirrorWithSig(vars);
return
_createMirror(
DataTypes.MirrorData(
@@ -541,11 +420,11 @@ contract LensHub is LensNFTBase, VersionedInitializable, LensMultiState, LensHub
* the NFT.
*/
function burnWithSig(uint256 tokenId, DataTypes.EIP712Signature calldata sig)
public
override
external
whenNotPaused
{
super.burnWithSig(tokenId, sig);
MetaTxLib.baseBurnWithSig(tokenId, sig);
_burn(tokenId);
_clearHandleHash(tokenId);
}
@@ -577,31 +456,7 @@ contract LensHub is LensNFTBase, VersionedInitializable, LensMultiState, LensHub
whenNotPaused
returns (uint256[] memory)
{
uint256 dataLength = vars.datas.length;
bytes32[] memory dataHashes = new bytes32[](dataLength);
for (uint256 i = 0; i < dataLength; ) {
dataHashes[i] = keccak256(vars.datas[i]);
unchecked {
++i;
}
}
unchecked {
_validateRecoveredAddress(
_calculateDigest(
keccak256(
abi.encode(
FOLLOW_WITH_SIG_TYPEHASH,
keccak256(abi.encodePacked(vars.profileIds)),
keccak256(abi.encodePacked(dataHashes)),
sigNonces[vars.follower]++,
vars.sig.deadline
)
)
),
vars.follower,
vars.sig
);
}
MetaTxLib.baseFollowWithSig(vars);
return
InteractionLogic.follow(
vars.follower,
@@ -637,24 +492,7 @@ contract LensHub is LensNFTBase, VersionedInitializable, LensMultiState, LensHub
whenNotPaused
returns (uint256)
{
unchecked {
_validateRecoveredAddress(
_calculateDigest(
keccak256(
abi.encode(
COLLECT_WITH_SIG_TYPEHASH,
vars.profileId,
vars.pubId,
keccak256(vars.data),
sigNonces[vars.collector]++,
vars.sig.deadline
)
)
),
vars.collector,
vars.sig
);
}
MetaTxLib.baseCollectWithSig(vars);
return
InteractionLogic.collect(
vars.collector,

View File

@@ -0,0 +1,135 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.10;
import {ILensHubNFTBase} from '../../interfaces/ILensHubNFTBase.sol';
import {Errors} from '../../libraries/Errors.sol';
import {DataTypes} from '../../libraries/DataTypes.sol';
import {Events} from '../../libraries/Events.sol';
import {ERC721Time} from './ERC721Time.sol';
import {ERC721Enumerable} from './ERC721Enumerable.sol';
/**
* @title LensNFTBase
* @author Lens Protocol
*
* @dev This is a trimmed down version of the LensNFTBase, mostly for contract size concerns.
* Meta transaction functions have been moved to the core LensHub to utilize the MetaTxLib library.
*
* @notice This is an abstract base contract to be inherited by other Lens Protocol NFTs, it includes
* the slightly modified ERC721Enumerable, which itself inherits from the ERC721Time-- which adds an
* internal operator approval setter, stores the mint timestamp for each token, and replaces the
* constructor with an initializer.
*/
abstract contract LensHubNFTBase is ERC721Enumerable, ILensHubNFTBase {
mapping(address => uint256) public sigNonces;
/**
* @notice Initializer sets the name, symbol and the cached domain separator.
*
* NOTE: Inheritor contracts *must* call this function to initialize the name & symbol in the
* inherited ERC721 contract.
*
* @param name The name to set in the ERC721 contract.
* @param symbol The symbol to set in the ERC721 contract.
*/
function _initialize(string calldata name, string calldata symbol) internal {
ERC721Time.__ERC721_Init(name, symbol);
emit Events.BaseInitialized(name, symbol, block.timestamp);
}
/// @inheritdoc ILensNFTBase
// function permit(
// address spender,
// uint256 tokenId,
// DataTypes.EIP712Signature calldata sig
// ) external override {
// if (spender == address(0)) revert Errors.ZeroSpender();
// address owner = ownerOf(tokenId);
// unchecked {
// _validateRecoveredAddress(
// _calculateDigest(
// keccak256(
// abi.encode(
// PERMIT_TYPEHASH,
// spender,
// tokenId,
// sigNonces[owner]++,
// sig.deadline
// )
// )
// ),
// owner,
// sig
// );
// }
// _approve(spender, tokenId);
// }
/// @inheritdoc ILensNFTBase
// function permitForAll(
// address owner,
// address operator,
// bool approved,
// DataTypes.EIP712Signature calldata sig
// ) external override {
// if (operator == address(0)) revert Errors.ZeroSpender();
// unchecked {
// _validateRecoveredAddress(
// _calculateDigest(
// keccak256(
// abi.encode(
// PERMIT_FOR_ALL_TYPEHASH,
// owner,
// operator,
// approved,
// sigNonces[owner]++,
// sig.deadline
// )
// )
// ),
// owner,
// sig
// );
// }
// _setOperatorApproval(owner, operator, approved);
// }
// / @inheritdoc ILensNFTBase
// function getDomainSeparator() external view override returns (bytes32) {
// return _calculateDomainSeparator();
// }
/// @inheritdoc ILensHubNFTBase
function burn(uint256 tokenId) public virtual override {
if (!_isApprovedOrOwner(msg.sender, tokenId)) revert Errors.NotOwnerOrApproved();
_burn(tokenId);
}
/// @inheritdoc ILensNFTBase
// function burnWithSig(uint256 tokenId, DataTypes.EIP712Signature calldata sig)
// public
// virtual
// override
// {
// address owner = ownerOf(tokenId);
// unchecked {
// _validateRecoveredAddress(
// _calculateDigest(
// keccak256(
// abi.encode(
// BURN_WITH_SIG_TYPEHASH,
// tokenId,
// sigNonces[owner]++,
// sig.deadline
// )
// )
// ),
// owner,
// sig
// );
// }
// _burn(tokenId);
// }
}

View File

@@ -55,7 +55,7 @@ abstract contract LensNFTBase is ERC721Enumerable, ILensNFTBase {
address spender,
uint256 tokenId,
DataTypes.EIP712Signature calldata sig
) external override {
) external virtual override {
if (spender == address(0)) revert Errors.ZeroSpender();
address owner = ownerOf(tokenId);
unchecked {

View File

@@ -0,0 +1,70 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.10;
import {DataTypes} from '../libraries/DataTypes.sol';
/**
* @title ILensNFTBase
* @author Lens Protocol
*
* @notice This is the interface for the LensNFTBase contract, from which all Lens NFTs inherit.
* It is an expansion of a very slightly modified ERC721Enumerable contract, which allows expanded
* meta-transaction functionality.
*/
interface ILensHubNFTBase {
// /**
// * @notice Implementation of an EIP-712 permit function for an ERC-721 NFT. We don't need to check
// * if the tokenId exists, since the function calls ownerOf(tokenId), which reverts if the tokenId does
// * not exist.
// *
// * @param spender The NFT spender.
// * @param tokenId The NFT token ID to approve.
// * @param sig The EIP712 signature struct.
// */
// function permit(
// address spender,
// uint256 tokenId,
// DataTypes.EIP712Signature calldata sig
// ) external;
// /**
// * @notice Implementation of an EIP-712 permit-style function for ERC-721 operator approvals. Allows
// * an operator address to control all NFTs a given owner owns.
// *
// * @param owner The owner to set operator approvals for.
// * @param operator The operator to approve.
// * @param approved Whether to approve or revoke approval from the operator.
// * @param sig The EIP712 signature struct.
// */
// function permitForAll(
// address owner,
// address operator,
// bool approved,
// DataTypes.EIP712Signature calldata sig
// ) external;
/**
* @notice Burns an NFT, removing it from circulation and essentially destroying it. This function can only
* be called by the NFT to burn's owner.
*
* @param tokenId The token ID of the token to burn.
*/
function burn(uint256 tokenId) external;
// /**
// * @notice Implementation of an EIP-712 permit-style function for token burning. Allows anyone to burn
// * a token on behalf of the owner with a signature.
// *
// * @param tokenId The token ID of the token to burn.
// * @param sig The EIP712 signature struct.
// */
// function burnWithSig(uint256 tokenId, DataTypes.EIP712Signature calldata sig) external;
// /**
// * @notice Returns the domain separator for this NFT contract.
// *
// * @return bytes32 The domain separator.
// */
// function getDomainSeparator() external view returns (bytes32);
}

View File

@@ -0,0 +1,536 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.10;
import {DataTypes} from './DataTypes.sol';
import {Errors} from './Errors.sol';
import '@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol';
/**
* @title MetaTxLib
*
* @author Lens Protocol (Zer0dot)
*
* @notice This library includes functions pertaining to meta-transactions to be called
* specifically from the LensHub.
*
* @dev The functions `getDomainSeparator`, `permit` and `permitForAll` have been absolutely delegated
* to this contract, but other `withSig` non-standard functions have only had their signature
* validation and nonce increment delegated to this contract.
*
* @dev It's also important to note that the _ownerOf() function does not validate against the zero
* address since the _validateRecoveredAddress() function reverts on a recovered zero address.
*/
library MetaTxLib {
// We store constants equal to the storage slots here to later access via inline
// assembly without needing to pass storage pointers. The NAME_SLOT_GT_31 slot
// is equivalent to keccak256(NAME_SLOT) and is where the name string is stored
// if the length is greater than 31 bytes.
uint256 internal constant NAME_SLOT = 0;
uint256 internal constant TOKEN_DATA_MAPPING_SLOT = 2;
uint256 internal constant APPROVAL_MAPPING_SLOT = 4;
uint256 internal constant OPERATOR_APPROVAL_MAPPING_SLOT = 5;
uint256 internal constant SIG_NONCES_MAPPING_SLOT = 10;
uint256 internal constant NAME_SLOT_GT_31 =
0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563;
// We also store typehashes here
bytes32 internal constant EIP712_REVISION_HASH = keccak256('1');
bytes32 internal constant PERMIT_TYPEHASH =
keccak256('Permit(address spender,uint256 tokenId,uint256 nonce,uint256 deadline)');
bytes32 internal constant PERMIT_FOR_ALL_TYPEHASH =
keccak256(
'PermitForAll(address owner,address operator,bool approved,uint256 nonce,uint256 deadline)'
);
bytes32 internal constant BURN_WITH_SIG_TYPEHASH =
keccak256('BurnWithSig(uint256 tokenId,uint256 nonce,uint256 deadline)');
bytes32 internal constant EIP712_DOMAIN_TYPEHASH =
keccak256(
'EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'
);
bytes32 internal constant SET_DEFAULT_PROFILE_WITH_SIG_TYPEHASH =
keccak256(
'SetDefaultProfileWithSig(address wallet,uint256 profileId,uint256 nonce,uint256 deadline)'
);
bytes32 internal constant SET_FOLLOW_MODULE_WITH_SIG_TYPEHASH =
keccak256(
'SetFollowModuleWithSig(uint256 profileId,address followModule,bytes followModuleInitData,uint256 nonce,uint256 deadline)'
);
bytes32 internal constant SET_FOLLOW_NFT_URI_WITH_SIG_TYPEHASH =
keccak256(
'SetFollowNFTURIWithSig(uint256 profileId,string followNFTURI,uint256 nonce,uint256 deadline)'
);
bytes32 internal constant SET_DISPATCHER_WITH_SIG_TYPEHASH =
keccak256(
'SetDispatcherWithSig(uint256 profileId,address dispatcher,uint256 nonce,uint256 deadline)'
);
bytes32 internal constant SET_PROFILE_IMAGE_URI_WITH_SIG_TYPEHASH =
keccak256(
'SetProfileImageURIWithSig(uint256 profileId,string imageURI,uint256 nonce,uint256 deadline)'
);
bytes32 internal constant POST_WITH_SIG_TYPEHASH =
keccak256(
'PostWithSig(uint256 profileId,string contentURI,address collectModule,bytes collectModuleInitData,address referenceModule,bytes referenceModuleInitData,uint256 nonce,uint256 deadline)'
);
bytes32 internal constant COMMENT_WITH_SIG_TYPEHASH =
keccak256(
'CommentWithSig(uint256 profileId,string contentURI,uint256 profileIdPointed,uint256 pubIdPointed,bytes referenceModuleData,address collectModule,bytes collectModuleInitData,address referenceModule,bytes referenceModuleInitData,uint256 nonce,uint256 deadline)'
);
bytes32 internal constant MIRROR_WITH_SIG_TYPEHASH =
keccak256(
'MirrorWithSig(uint256 profileId,uint256 profileIdPointed,uint256 pubIdPointed,bytes referenceModuleData,address referenceModule,bytes referenceModuleInitData,uint256 nonce,uint256 deadline)'
);
bytes32 internal constant FOLLOW_WITH_SIG_TYPEHASH =
keccak256(
'FollowWithSig(uint256[] profileIds,bytes[] datas,uint256 nonce,uint256 deadline)'
);
bytes32 internal constant COLLECT_WITH_SIG_TYPEHASH =
keccak256(
'CollectWithSig(uint256 profileId,uint256 pubId,bytes data,uint256 nonce,uint256 deadline)'
);
function permit(
address spender,
uint256 tokenId,
DataTypes.EIP712Signature calldata sig
) external {
if (spender == address(0)) revert Errors.ZeroSpender();
address owner = _ownerOf(tokenId);
_validateRecoveredAddress(
_calculateDigest(
keccak256(
abi.encode(PERMIT_TYPEHASH, spender, tokenId, _sigNonces(owner), sig.deadline)
)
),
owner,
sig
);
_approve(spender, tokenId);
}
function permitForAll(
address owner,
address operator,
bool approved,
DataTypes.EIP712Signature calldata sig
) external {
if (operator == address(0)) revert Errors.ZeroSpender();
_validateRecoveredAddress(
_calculateDigest(
keccak256(
abi.encode(
PERMIT_FOR_ALL_TYPEHASH,
owner,
operator,
approved,
_sigNonces(owner),
sig.deadline
)
)
),
owner,
sig
);
_setOperatorApproval(owner, operator, approved);
}
function baseSetDefaultProfileWithSig(DataTypes.SetDefaultProfileWithSigData calldata vars)
external
{
_validateRecoveredAddress(
_calculateDigest(
keccak256(
abi.encode(
SET_DEFAULT_PROFILE_WITH_SIG_TYPEHASH,
vars.wallet,
vars.profileId,
_sigNonces(vars.wallet),
vars.sig.deadline
)
)
),
vars.wallet,
vars.sig
);
}
function baseSetFollowModuleWithSig(DataTypes.SetFollowModuleWithSigData calldata vars)
external
{
address owner = _ownerOf(vars.profileId);
_validateRecoveredAddress(
_calculateDigest(
keccak256(
abi.encode(
SET_FOLLOW_MODULE_WITH_SIG_TYPEHASH,
vars.profileId,
vars.followModule,
keccak256(vars.followModuleInitData),
_sigNonces(owner),
vars.sig.deadline
)
)
),
owner,
vars.sig
);
}
function baseSetDispatcherWithSig(DataTypes.SetDispatcherWithSigData calldata vars) external {
address owner = _ownerOf(vars.profileId);
_validateRecoveredAddress(
_calculateDigest(
keccak256(
abi.encode(
SET_DISPATCHER_WITH_SIG_TYPEHASH,
vars.profileId,
vars.dispatcher,
_sigNonces(owner),
vars.sig.deadline
)
)
),
owner,
vars.sig
);
}
function baseSetProfileImageURIWithSig(DataTypes.SetProfileImageURIWithSigData calldata vars)
external
{
address owner = _ownerOf(vars.profileId);
_validateRecoveredAddress(
_calculateDigest(
keccak256(
abi.encode(
SET_PROFILE_IMAGE_URI_WITH_SIG_TYPEHASH,
vars.profileId,
keccak256(bytes(vars.imageURI)),
_sigNonces(owner),
vars.sig.deadline
)
)
),
owner,
vars.sig
);
}
function baseSetFollowNFTURIWithSig(DataTypes.SetFollowNFTURIWithSigData calldata vars)
external
{
address owner = _ownerOf(vars.profileId);
_validateRecoveredAddress(
_calculateDigest(
keccak256(
abi.encode(
SET_FOLLOW_NFT_URI_WITH_SIG_TYPEHASH,
vars.profileId,
keccak256(bytes(vars.followNFTURI)),
_sigNonces(owner),
vars.sig.deadline
)
)
),
owner,
vars.sig
);
}
function basePostWithSig(DataTypes.PostWithSigData calldata vars) external {
address owner = _ownerOf(vars.profileId);
unchecked {
_validateRecoveredAddress(
_calculateDigest(
keccak256(
abi.encode(
POST_WITH_SIG_TYPEHASH,
vars.profileId,
keccak256(bytes(vars.contentURI)),
vars.collectModule,
keccak256(vars.collectModuleInitData),
vars.referenceModule,
keccak256(vars.referenceModuleInitData),
_sigNonces(owner),
vars.sig.deadline
)
)
),
owner,
vars.sig
);
}
}
function baseCommentWithSig(DataTypes.CommentWithSigData calldata vars) external {
address owner = _ownerOf(vars.profileId);
_validateRecoveredAddress(
_calculateDigest(
keccak256(
abi.encode(
COMMENT_WITH_SIG_TYPEHASH,
vars.profileId,
keccak256(bytes(vars.contentURI)),
vars.profileIdPointed,
vars.pubIdPointed,
keccak256(vars.referenceModuleData),
vars.collectModule,
keccak256(vars.collectModuleInitData),
vars.referenceModule,
keccak256(vars.referenceModuleInitData),
_sigNonces(owner),
vars.sig.deadline
)
)
),
owner,
vars.sig
);
}
function baseMirrorWithSig(DataTypes.MirrorWithSigData calldata vars) external {
address owner = _ownerOf(vars.profileId);
_validateRecoveredAddress(
_calculateDigest(
keccak256(
abi.encode(
MIRROR_WITH_SIG_TYPEHASH,
vars.profileId,
vars.profileIdPointed,
vars.pubIdPointed,
keccak256(vars.referenceModuleData),
vars.referenceModule,
keccak256(vars.referenceModuleInitData),
_sigNonces(owner),
vars.sig.deadline
)
)
),
owner,
vars.sig
);
}
function baseBurnWithSig(uint256 tokenId, DataTypes.EIP712Signature calldata sig) external {
address owner = _ownerOf(tokenId);
_validateRecoveredAddress(
_calculateDigest(
keccak256(
abi.encode(
BURN_WITH_SIG_TYPEHASH,
tokenId,
_sigNonces(owner),
sig.deadline
)
)
),
owner,
sig
);
}
function baseFollowWithSig(DataTypes.FollowWithSigData calldata vars) external {
uint256 dataLength = vars.datas.length;
bytes32[] memory dataHashes = new bytes32[](dataLength);
for (uint256 i = 0; i < dataLength; ) {
dataHashes[i] = keccak256(vars.datas[i]);
unchecked {
++i;
}
}
_validateRecoveredAddress(
_calculateDigest(
keccak256(
abi.encode(
FOLLOW_WITH_SIG_TYPEHASH,
keccak256(abi.encodePacked(vars.profileIds)),
keccak256(abi.encodePacked(dataHashes)),
_sigNonces(vars.follower),
vars.sig.deadline
)
)
),
vars.follower,
vars.sig
);
}
function baseCollectWithSig(DataTypes.CollectWithSigData calldata vars) external {
_validateRecoveredAddress(
_calculateDigest(
keccak256(
abi.encode(
COLLECT_WITH_SIG_TYPEHASH,
vars.profileId,
vars.pubId,
keccak256(vars.data),
_sigNonces(vars.collector),
vars.sig.deadline
)
)
),
vars.collector,
vars.sig
);
}
function getDomainSeparator() external view returns (bytes32) {
return _calculateDomainSeparator();
}
/**
* @dev Wrapper for ecrecover to reduce code size, used in meta-tx specific functions.
*/
function _validateRecoveredAddress(
bytes32 digest,
address expectedAddress,
DataTypes.EIP712Signature calldata sig
) private view {
if (sig.deadline < block.timestamp) revert Errors.SignatureExpired();
address recoveredAddress = ecrecover(digest, sig.v, sig.r, sig.s);
if (recoveredAddress == address(0) || recoveredAddress != expectedAddress)
revert Errors.SignatureInvalid();
}
/**
* @dev Calculates EIP712 DOMAIN_SEPARATOR based on the current contract and chain ID.
*/
function _calculateDomainSeparator() private view returns (bytes32) {
return
keccak256(
abi.encode(
EIP712_DOMAIN_TYPEHASH,
keccak256(_nameBytes()),
EIP712_REVISION_HASH,
block.chainid,
address(this)
)
);
}
/**
* @dev Calculates EIP712 digest based on the current DOMAIN_SEPARATOR.
*
* @param hashedMessage The message hash from which the digest should be calculated.
*
* @return bytes32 A 32-byte output representing the EIP712 digest.
*/
function _calculateDigest(bytes32 hashedMessage) private view returns (bytes32) {
bytes32 digest;
unchecked {
digest = keccak256(
abi.encodePacked('\x19\x01', _calculateDomainSeparator(), hashedMessage)
);
}
return digest;
}
function _nameBytes() private view returns (bytes memory) {
bytes memory ptr;
assembly {
// Load the free memory pointer, where we'll return the string
ptr := mload(64)
// Load the slot, which either contains the name + 2*length if length < 32 or
// 2*length+1 if length >= 32, and the actual string starts at slot keccak256(NAME_SLOT)
let slotLoad := sload(NAME_SLOT)
// Determine if the length > 32 by checking the lowest order bit, meaning the string
// itself is stored at keccak256(NAME_SLOT)
let isNotSameSlot := and(slotLoad, 1)
switch isNotSameSlot
case 0 {
// The name is in the same slot
// Determine the size by dividing the last byte's value by 2
let size := shr(1, and(slotLoad, 255))
// Store the size in the first slot
mstore(ptr, size)
// Store the actual string in the second slot (without the size)
mstore(add(ptr, 32), and(slotLoad, not(255)))
// Store the new memory pointer in the free memory pointer slot
mstore(64, add(add(ptr, 32), size))
}
case 1 {
// The name is not in the same slot
// Determine the size by dividing the value in the whole slot minus 1 by 2
let size := shr(1, sub(slotLoad, 1))
// Store the size in the first slot
mstore(ptr, size)
// Compute the total memory slots we need, this is (size + 31) / 32
let totalMemorySlots := shr(5, add(size, 31))
// Iterate through the words in memory and store the string word by word
for {
let i := 0
} lt(i, totalMemorySlots) {
i := add(i, 1)
} {
mstore(add(add(ptr, 32), mul(32, i)), sload(add(NAME_SLOT_GT_31, i)))
}
// Store the new memory pointer in the free memory pointer slot
mstore(64, add(add(ptr, 32), size))
}
}
// Return a memory pointer to the name (which always starts with the size at the first slot)
return ptr;
}
function _sigNonces(address owner) private returns (uint256) {
uint256 previousValue;
assembly {
mstore(0, owner)
mstore(32, SIG_NONCES_MAPPING_SLOT)
let slot := keccak256(0, 64)
previousValue := sload(slot)
sstore(slot, add(previousValue, 1))
}
return previousValue;
}
function _approve(address spender, uint256 tokenId) private {
assembly {
mstore(0, tokenId)
mstore(32, APPROVAL_MAPPING_SLOT)
let slot := keccak256(0, 64)
sstore(slot, spender)
}
}
function _setOperatorApproval(
address owner,
address operator,
bool approved
) private {
assembly {
mstore(0, owner)
mstore(32, OPERATOR_APPROVAL_MAPPING_SLOT)
mstore(32, keccak256(0, 64))
mstore(0, operator)
let slot := keccak256(0, 64)
sstore(slot, approved)
}
}
function _ownerOf(uint256 tokenId) private view returns (address) {
// Note that this does *not* include a zero address check, but this is acceptable because
// _validateRecoveredAddress reverts on recovering a zero address.
address owner;
assembly {
mstore(0, tokenId)
mstore(32, TOKEN_DATA_MAPPING_SLOT)
let slot := keccak256(0, 64)
// this weird bit shift is necessary to remove the packing from the variable
owner := shr(96, shl(96, sload(slot)))
}
return owner;
}
}

View File

@@ -16,6 +16,7 @@ import 'hardhat-gas-reporter';
import 'hardhat-contract-sizer';
import 'hardhat-log-remover';
import 'hardhat-spdx-license-identifier';
import 'hardhat-tracer';
if (!process.env.SKIP_LOAD) {
glob.sync('./tasks/**/*.ts').forEach(function (file) {
@@ -23,7 +24,6 @@ if (!process.env.SKIP_LOAD) {
});
}
const DEFAULT_BLOCK_GAS_LIMIT = 12450000;
const MNEMONIC_PATH = "m/44'/60'/0'/0";
const MNEMONIC = process.env.MNEMONIC || '';
const MAINNET_FORK = process.env.MAINNET_FORK === 'true';
@@ -73,11 +73,6 @@ const config: HardhatUserConfig = {
mumbai: getCommonNetworkConfig(ePolygonNetwork.mumbai, 80001),
xdai: getCommonNetworkConfig(eXDaiNetwork.xdai, 100),
hardhat: {
hardfork: 'london',
blockGasLimit: DEFAULT_BLOCK_GAS_LIMIT,
gas: DEFAULT_BLOCK_GAS_LIMIT,
gasPrice: 8000000000,
chainId: HARDHATEVM_CHAINID,
throwOnTransactionFailures: true,
throwOnCallFailures: true,
accounts: accounts.map(({ secretKey, balance }: { secretKey: string; balance: string }) => ({
@@ -85,6 +80,7 @@ const config: HardhatUserConfig = {
balance,
})),
forking: mainnetFork,
allowUnlimitedContractSize: true,
},
},
gasReporter: {

5602
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -56,6 +56,7 @@
"hardhat-gas-reporter": "1.0.6",
"hardhat-log-remover": "2.0.2",
"hardhat-spdx-license-identifier": "2.0.3",
"hardhat-tracer": "^1.1.0-rc.6",
"husky": "7.0.4",
"prettier": "2.5.0",
"prettier-plugin-solidity": "1.0.0-beta.19",

View File

@@ -26,6 +26,7 @@ import {
ProfileFollowModule__factory,
RevertFollowModule__factory,
ProfileCreationProxy__factory,
MetaTxLib__factory,
} from '../typechain-types';
import { deployWithVerify, waitForTx } from './helpers/utils';
@@ -92,11 +93,17 @@ task('full-deploy-verify', 'deploys the entire Lens Protocol with explorer verif
[],
'contracts/libraries/ProfileTokenURILogic.sol:ProfileTokenURILogic'
);
const metaTxLib = await deployWithVerify(
new MetaTxLib__factory(deployer).deploy({ nonce: deployerNonce++ }),
[],
'contracts/libraries/MetaTxLib.sol:MetaTxLib'
);
const hubLibs = {
'contracts/libraries/PublishingLogic.sol:PublishingLogic': publishingLogic.address,
'contracts/libraries/InteractionLogic.sol:InteractionLogic': interactionLogic.address,
'contracts/libraries/ProfileTokenURILogic.sol:ProfileTokenURILogic':
profileTokenURILogic.address,
'contracts/libraries/MetaTxLib.sol:MetaTxLib': metaTxLib.address,
};
// Here, we pre-compute the nonces and addresses used to deploy the contracts.

View File

@@ -26,6 +26,7 @@ import {
ProfileFollowModule__factory,
RevertFollowModule__factory,
ProfileCreationProxy__factory,
MetaTxLib__factory,
} from '../typechain-types';
import { deployContract, waitForTx } from './helpers/utils';
@@ -68,11 +69,15 @@ task('full-deploy', 'deploys the entire Lens Protocol').setAction(async ({}, hre
const profileTokenURILogic = await deployContract(
new ProfileTokenURILogic__factory(deployer).deploy({ nonce: deployerNonce++ })
);
const metaTxLib = await deployContract(
new MetaTxLib__factory(deployer).deploy({ nonce: deployerNonce++ })
);
const hubLibs = {
'contracts/libraries/PublishingLogic.sol:PublishingLogic': publishingLogic.address,
'contracts/libraries/InteractionLogic.sol:InteractionLogic': interactionLogic.address,
'contracts/libraries/ProfileTokenURILogic.sol:ProfileTokenURILogic':
profileTokenURILogic.address,
'contracts/libraries/MetaTxLib.sol:MetaTxLib': metaTxLib.address,
};
// Here, we pre-compute the nonces and addresses used to deploy the contracts.

98
tasks/list-storage.ts Normal file
View File

@@ -0,0 +1,98 @@
import '@nomiclabs/hardhat-ethers';
import { hexlify, keccak256, RLP } from 'ethers/lib/utils';
import { task } from 'hardhat/config';
import {
LensHub__factory,
PublishingLogic__factory,
InteractionLogic__factory,
ProfileTokenURILogic__factory,
FollowNFT__factory,
TransparentUpgradeableProxy__factory,
MetaTxLib__factory,
} from '../typechain-types';
import { deployContract, waitForTx } from './helpers/utils';
task('list-storage', '').setAction(async ({}, hre) => {
const ethers = hre.ethers;
const accounts = await ethers.getSigners();
const deployer = accounts[0];
const governance = accounts[1];
const proxyAdminAddress = deployer.address;
// Nonce management in case of deployment issues
let deployerNonce = await ethers.provider.getTransactionCount(deployer.address);
console.log('\n\t-- Deploying Logic Libs --');
const publishingLogic = await deployContract(
new PublishingLogic__factory(deployer).deploy({ nonce: deployerNonce++ })
);
const interactionLogic = await deployContract(
new InteractionLogic__factory(deployer).deploy({ nonce: deployerNonce++ })
);
const profileTokenURILogic = await deployContract(
new ProfileTokenURILogic__factory(deployer).deploy({ nonce: deployerNonce++ })
);
const metaTxLib = await deployContract(
new MetaTxLib__factory(deployer).deploy({ nonce: deployerNonce++ })
);
const hubLibs = {
'contracts/libraries/PublishingLogic.sol:PublishingLogic': publishingLogic.address,
'contracts/libraries/InteractionLogic.sol:InteractionLogic': interactionLogic.address,
'contracts/libraries/ProfileTokenURILogic.sol:ProfileTokenURILogic':
profileTokenURILogic.address,
'contracts/libraries/MetaTxLib.sol:MetaTxLib': metaTxLib.address,
};
// Here, we pre-compute the nonces and addresses used to deploy the contracts.
// const nonce = await deployer.getTransactionCount();
const followNFTNonce = hexlify(deployerNonce + 1);
const collectNFTNonce = hexlify(deployerNonce + 2);
const hubProxyNonce = hexlify(deployerNonce + 3);
const followNFTImplAddress =
'0x' + keccak256(RLP.encode([deployer.address, followNFTNonce])).substr(26);
const collectNFTImplAddress =
'0x' + keccak256(RLP.encode([deployer.address, collectNFTNonce])).substr(26);
const hubProxyAddress =
'0x' + keccak256(RLP.encode([deployer.address, hubProxyNonce])).substr(26);
// Next, we deploy first the hub implementation, then the followNFT implementation, the collectNFT, and finally the
// hub proxy with initialization.
console.log('\n\t-- Deploying Hub Implementation --');
const lensHubImpl = await deployContract(
new LensHub__factory(hubLibs, deployer).deploy(followNFTImplAddress, collectNFTImplAddress, {
nonce: deployerNonce++,
})
);
console.log('\n\t-- Deploying Follow & Collect NFT Implementations --');
await deployContract(
new FollowNFT__factory(deployer).deploy(hubProxyAddress, { nonce: deployerNonce++ })
);
await deployContract(
new FollowNFT__factory(deployer).deploy(hubProxyAddress, { nonce: deployerNonce++ })
);
let data = lensHubImpl.interface.encodeFunctionData('initialize', [
'Lens Protocol Profiles',
'LPP',
governance.address,
]);
console.log('\n\t-- Deploying Hub Proxy --');
let proxy = await deployContract(
new TransparentUpgradeableProxy__factory(deployer).deploy(
lensHubImpl.address,
proxyAdminAddress,
data,
{ nonce: deployerNonce++ }
)
);
for (let i = 0; i < 100; ++i) {
const storageSlot = await ethers.provider.getStorageAt(proxy.address, i);
console.log(`Hub proxy storage at slot ${i}: ${storageSlot}`);
}
});

View File

@@ -26,6 +26,7 @@ import {
UIDataProvider__factory,
ProfileFollowModule__factory,
RevertFollowModule__factory,
MetaTxLib__factory,
} from '../typechain-types';
import { deployWithVerify, waitForTx } from './helpers/utils';
@@ -91,11 +92,17 @@ task(
[],
'contracts/libraries/ProfileTokenURILogic.sol:ProfileTokenURILogic'
);
const metaTxLib = await deployWithVerify(
new MetaTxLib__factory(deployer).deploy({ nonce: deployerNonce++ }),
[],
'contracts/libraries/MetaTxLib.sol:MetaTxLib'
);
const hubLibs = {
'contracts/libraries/PublishingLogic.sol:PublishingLogic': publishingLogic.address,
'contracts/libraries/InteractionLogic.sol:InteractionLogic': interactionLogic.address,
'contracts/libraries/ProfileTokenURILogic.sol:ProfileTokenURILogic':
profileTokenURILogic.address,
'contracts/libraries/MetaTxLib.sol:MetaTxLib': metaTxLib.address,
};
// Here, we pre-compute the nonces and addresses used to deploy the contracts.

View File

@@ -1,4 +1,4 @@
import { AbiCoder } from '@ethersproject/contracts/node_modules/@ethersproject/abi';
import { AbiCoder } from 'ethers/lib/utils';
import { parseEther } from '@ethersproject/units';
import '@nomiclabs/hardhat-ethers';
import { expect, use } from 'chai';
@@ -39,6 +39,7 @@ import {
ModuleGlobals__factory,
ProfileTokenURILogic__factory,
PublishingLogic__factory,
MetaTxLib__factory,
RevertCollectModule,
RevertCollectModule__factory,
TimedFeeCollectModule,
@@ -167,11 +168,13 @@ before(async function () {
const publishingLogic = await new PublishingLogic__factory(deployer).deploy();
const interactionLogic = await new InteractionLogic__factory(deployer).deploy();
const profileTokenURILogic = await new ProfileTokenURILogic__factory(deployer).deploy();
const metaTxLib = await new MetaTxLib__factory(deployer).deploy();
hubLibs = {
'contracts/libraries/PublishingLogic.sol:PublishingLogic': publishingLogic.address,
'contracts/libraries/InteractionLogic.sol:InteractionLogic': interactionLogic.address,
'contracts/libraries/ProfileTokenURILogic.sol:ProfileTokenURILogic':
profileTokenURILogic.address,
'contracts/libraries/MetaTxLib.sol:MetaTxLib': metaTxLib.address,
};
// Here, we pre-compute the nonces and addresses used to deploy the contracts.

View File

@@ -1,6 +1,5 @@
import '@nomiclabs/hardhat-ethers';
import { expect } from 'chai';
import { BigNumber } from 'ethers';
import { FollowNFT__factory } from '../../../typechain-types';
import { MAX_UINT256, ZERO_ADDRESS } from '../../helpers/constants';
import { ERRORS } from '../../helpers/errors';

View File

@@ -1,4 +1,5 @@
import '@nomiclabs/hardhat-ethers';
import hre from 'hardhat';
import { expect } from 'chai';
import { MAX_UINT256, ZERO_ADDRESS } from '../../helpers/constants';
import { ERRORS } from '../../helpers/errors';
@@ -699,6 +700,9 @@ makeSuiteCleanRoom('Publishing Comments', function () {
MAX_UINT256
);
// hre.tracer.enabled = true;
// hre.tracer.sloads = true;
// hre.tracer.sstores = true;
await cancelWithPermitForAll();
await expect(

View File

@@ -1,4 +1,4 @@
import { BigNumber } from '@ethersproject/contracts/node_modules/@ethersproject/bignumber';
import { BigNumber } from 'ethers';
import { parseEther } from '@ethersproject/units';
import '@nomiclabs/hardhat-ethers';
import { expect } from 'chai';

View File

@@ -1,4 +1,4 @@
import { BigNumber } from '@ethersproject/contracts/node_modules/@ethersproject/bignumber';
import { BigNumber } from 'ethers';
import { parseEther } from '@ethersproject/units';
import '@nomiclabs/hardhat-ethers';
import { expect } from 'chai';

View File

@@ -1,4 +1,4 @@
import { BigNumber } from '@ethersproject/contracts/node_modules/@ethersproject/bignumber';
import { BigNumber } from 'ethers';
import { parseEther } from '@ethersproject/units';
import '@nomiclabs/hardhat-ethers';
import { expect } from 'chai';

View File

@@ -1,4 +1,4 @@
import { BigNumber } from '@ethersproject/contracts/node_modules/@ethersproject/bignumber';
import { BigNumber } from 'ethers';
import { parseEther } from '@ethersproject/units';
import '@nomiclabs/hardhat-ethers';
import { expect } from 'chai';

View File

@@ -1,4 +1,4 @@
import { BigNumber } from '@ethersproject/contracts/node_modules/@ethersproject/bignumber';
import { BigNumber } from 'ethers';
import { parseEther } from '@ethersproject/units';
import '@nomiclabs/hardhat-ethers';
import { expect } from 'chai';

View File

@@ -1,4 +1,5 @@
import '@nomiclabs/hardhat-ethers';
import hre from 'hardhat';
import { expect } from 'chai';
import { keccak256, toUtf8Bytes } from 'ethers/lib/utils';
import { MAX_UINT256, ZERO_ADDRESS } from '../helpers/constants';
@@ -23,6 +24,7 @@ import {
user,
userAddress,
} from '../__setup.spec';
import { hardhatArguments } from 'hardhat';
makeSuiteCleanRoom('Lens NFT Base Functionality', function () {
context('generic', function () {
@@ -104,7 +106,7 @@ makeSuiteCleanRoom('Lens NFT Base Functionality', function () {
s,
deadline: MAX_UINT256,
})
).to.be.revertedWith(ERRORS.ERC721_QUERY_FOR_NONEXISTENT_TOKEN);
).to.be.revertedWith(ERRORS.SIGNATURE_INVALID);
});
it('TestWallet should fail to permit with signature deadline mismatch', async function () {
@@ -326,7 +328,7 @@ makeSuiteCleanRoom('Lens NFT Base Functionality', function () {
);
await expect(lensHub.burnWithSig(0, { v, r, s, deadline: MAX_UINT256 })).to.be.revertedWith(
ERRORS.ERC721_QUERY_FOR_NONEXISTENT_TOKEN
ERRORS.SIGNATURE_INVALID
);
});
@@ -409,14 +411,18 @@ makeSuiteCleanRoom('Lens NFT Base Functionality', function () {
nonce,
MAX_UINT256
);
await expect(
lensHub.permit(userAddress, FIRST_PROFILE_ID, {
v,
r,
s,
deadline: MAX_UINT256,
})
lensHub.permit(
userAddress,
FIRST_PROFILE_ID,
{
v,
r,
s,
deadline: MAX_UINT256,
},
{ gasLimit: 12450000 }
)
).to.not.be.reverted;
await expect(