Files
core/contracts/misc/LensPeripheryDataProvider.sol
2022-03-16 19:22:07 -04:00

138 lines
5.1 KiB
Solidity

// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity 0.8.10;
import {IERC721Time} from '../core/base/IERC721Time.sol';
import {ILensHub} from '../interfaces/ILensHub.sol';
import {DataTypes} from '../libraries/DataTypes.sol';
import {Events} from '../libraries/Events.sol';
import {Errors} from '../libraries/Errors.sol';
/**
* @notice This is a peripheral contract that allows for users to emit an event demonstrating whether or not
* they explicitly want a follow to be shown.
*
* @dev This is useful because it allows clients to filter out follow NFTs that were transferred to
* a recipient by another user (i.e. Not a mint) and not register them as "following" unless
* the recipient explicitly toggles the follow here.
*/
contract LensPeripheryDataProvider {
string public constant NAME = 'LensPeripheryDataProvider';
bytes32 internal constant EIP712_REVISION_HASH = keccak256('1');
bytes32 internal constant EIP712_DOMAIN_TYPEHASH =
keccak256(
'EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'
);
bytes32 internal constant TOGGLE_FOLLOW_WITH_SIG_TYPEHASH =
keccak256(
'ToggleFollowWithSig(uint256[] profileIds,bool[] enables,uint256 nonce,uint256 deadline)'
);
ILensHub immutable HUB;
mapping(address => uint256) public sigNonces;
constructor(ILensHub hub) {
HUB = hub;
}
/**
* @notice Toggle Follows on the given profiles, emiting toggle event for each FollowNFT.
*
* NOTE: `profileIds`, `followNFTIds` and `enables` arrays must be of the same length.
*
* @param profileIds The token ID array of the profiles.
* @param enables The array of booleans to enable/disable follows.
*/
function toggleFollow(uint256[] calldata profileIds, bool[] calldata enables) external {
_toggleFollow(msg.sender, profileIds, enables);
}
/**
* @notice Toggle Follows a given profiles via signature with the specified parameters.
*
* @param vars A ToggleFollowWithSigData struct containing the regular parameters as well as the signing follower's address
* and an EIP712Signature struct.
*/
function toggleFollowWithSig(DataTypes.ToggleFollowWithSigData calldata vars) external {
_validateRecoveredAddress(
_calculateDigest(
keccak256(
abi.encode(
TOGGLE_FOLLOW_WITH_SIG_TYPEHASH,
keccak256(abi.encodePacked(vars.profileIds)),
keccak256(abi.encodePacked(vars.enables)),
sigNonces[vars.follower]++,
vars.sig.deadline
)
)
),
vars.follower,
vars.sig
);
_toggleFollow(vars.follower, vars.profileIds, vars.enables);
}
function _toggleFollow(
address follower,
uint256[] calldata profileIds,
bool[] calldata enables
) internal {
if (profileIds.length != enables.length) revert Errors.ArrayMismatch();
for (uint256 i = 0; i < profileIds.length; ++i) {
address followNFT = HUB.getFollowNFT(profileIds[i]);
if (followNFT == address(0)) revert Errors.FollowInvalid();
if (!IERC721Time(address(HUB)).exists(profileIds[i])) revert Errors.TokenDoesNotExist();
if (IERC721Time(followNFT).balanceOf(follower) == 0) revert Errors.FollowInvalid();
}
emit Events.FollowsToggled(follower, profileIds, enables, block.timestamp);
}
/**
* @dev Wrapper for ecrecover to reduce code size, used in meta-tx specific functions.
*/
function _validateRecoveredAddress(
bytes32 digest,
address expectedAddress,
DataTypes.EIP712Signature memory sig
) internal 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() internal view returns (bytes32) {
return
keccak256(
abi.encode(
EIP712_DOMAIN_TYPEHASH,
keccak256(bytes(NAME)),
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) internal view returns (bytes32) {
bytes32 digest;
unchecked {
digest = keccak256(
abi.encodePacked('\x19\x01', _calculateDomainSeparator(), hashedMessage)
);
}
return digest;
}
}