mirror of
https://github.com/lens-protocol/core.git
synced 2026-04-22 03:02:03 -04:00
Merge branch 'main' into feat/collect-modules-toggle-follower-only
This commit is contained in:
@@ -2,6 +2,8 @@ module.exports = {
|
||||
skipFiles: [
|
||||
'/core/base/ERC721Time.sol',
|
||||
'/core/base/ERC721Enumerable.sol',
|
||||
'/core/modules/follow/SecretCodeFollowModule.sol',
|
||||
'/upgradeability',
|
||||
'/interfaces',
|
||||
'/mocks',
|
||||
],
|
||||
|
||||
@@ -28,6 +28,7 @@ contract CollectNFT is ICollectNFT, LensNFTBase {
|
||||
// to initialize the hub proxy at construction.
|
||||
constructor(address hub) {
|
||||
HUB = hub;
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
/// @inheritdoc ICollectNFT
|
||||
|
||||
@@ -9,7 +9,6 @@ import {Errors} from '../libraries/Errors.sol';
|
||||
import {Events} from '../libraries/Events.sol';
|
||||
import {DataTypes} from '../libraries/DataTypes.sol';
|
||||
import {LensNFTBase} from './base/LensNFTBase.sol';
|
||||
import {IERC721Metadata} from '@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol';
|
||||
|
||||
/**
|
||||
* @title FollowNFT
|
||||
@@ -47,6 +46,7 @@ contract FollowNFT is LensNFTBase, IFollowNFT {
|
||||
// We create the FollowNFT with the pre-computed HUB address before deploying the hub.
|
||||
constructor(address hub) {
|
||||
HUB = hub;
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
/// @inheritdoc IFollowNFT
|
||||
|
||||
@@ -940,7 +940,7 @@ contract LensHub is ILensHub, LensNFTBase, VersionedInitializable, LensMultiStat
|
||||
address from,
|
||||
address to,
|
||||
uint256 tokenId
|
||||
) internal override {
|
||||
) internal override whenNotPaused {
|
||||
if (_dispatcherByProfile[tokenId] != address(0)) {
|
||||
_setDispatcher(tokenId, address(0));
|
||||
}
|
||||
|
||||
@@ -9,8 +9,10 @@ import {Errors} from '../../libraries/Errors.sol';
|
||||
/**
|
||||
* @title LensMultiState
|
||||
*
|
||||
* @notice This is an abstract contract that implements internal LensHub state setting and
|
||||
* validation.
|
||||
* @notice This is an abstract contract that implements internal LensHub state setting and validation.
|
||||
*
|
||||
* whenNotPaused: Either publishingPaused or Unpaused.
|
||||
* whenPublishingEnabled: When Unpaused only.
|
||||
*/
|
||||
abstract contract LensMultiState {
|
||||
DataTypes.ProtocolState private _state;
|
||||
@@ -39,9 +41,7 @@ abstract contract LensMultiState {
|
||||
}
|
||||
|
||||
function _validatePublishingEnabled() internal view {
|
||||
if (_state == DataTypes.ProtocolState.Paused) {
|
||||
revert Errors.Paused();
|
||||
} else if (_state == DataTypes.ProtocolState.PublishingPaused) {
|
||||
if (_state != DataTypes.ProtocolState.Unpaused) {
|
||||
revert Errors.PublishingPaused();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ contract FeeCollectModule is ICollectModule, FeeModuleBase, FollowValidationModu
|
||||
!_currencyWhitelisted(currency) ||
|
||||
recipient == address(0) ||
|
||||
referralFee > BPS_MAX ||
|
||||
amount < BPS_MAX
|
||||
amount == 0
|
||||
) revert Errors.InitParamsInvalid();
|
||||
|
||||
_dataByPublicationByProfile[profileId][pubId].amount = amount;
|
||||
@@ -141,7 +141,8 @@ contract FeeCollectModule is ICollectModule, FeeModuleBase, FollowValidationModu
|
||||
uint256 adjustedAmount = amount - treasuryAmount;
|
||||
|
||||
IERC20(currency).safeTransferFrom(collector, recipient, adjustedAmount);
|
||||
IERC20(currency).safeTransferFrom(collector, treasury, treasuryAmount);
|
||||
if (treasuryAmount > 0)
|
||||
IERC20(currency).safeTransferFrom(collector, treasury, treasuryAmount);
|
||||
}
|
||||
|
||||
function _processCollectWithReferral(
|
||||
@@ -181,6 +182,7 @@ contract FeeCollectModule is ICollectModule, FeeModuleBase, FollowValidationModu
|
||||
address recipient = _dataByPublicationByProfile[profileId][pubId].recipient;
|
||||
|
||||
IERC20(currency).safeTransferFrom(collector, recipient, adjustedAmount);
|
||||
IERC20(currency).safeTransferFrom(collector, treasury, treasuryAmount);
|
||||
if (treasuryAmount > 0)
|
||||
IERC20(currency).safeTransferFrom(collector, treasury, treasuryAmount);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ contract LimitedFeeCollectModule is ICollectModule, FeeModuleBase, FollowValidat
|
||||
!_currencyWhitelisted(currency) ||
|
||||
recipient == address(0) ||
|
||||
referralFee > BPS_MAX ||
|
||||
amount < BPS_MAX
|
||||
amount == 0
|
||||
) revert Errors.InitParamsInvalid();
|
||||
|
||||
_dataByPublicationByProfile[profileId][pubId].collectLimit = collectLimit;
|
||||
@@ -156,7 +156,8 @@ contract LimitedFeeCollectModule is ICollectModule, FeeModuleBase, FollowValidat
|
||||
uint256 adjustedAmount = amount - treasuryAmount;
|
||||
|
||||
IERC20(currency).safeTransferFrom(collector, recipient, adjustedAmount);
|
||||
IERC20(currency).safeTransferFrom(collector, treasury, treasuryAmount);
|
||||
if (treasuryAmount > 0)
|
||||
IERC20(currency).safeTransferFrom(collector, treasury, treasuryAmount);
|
||||
}
|
||||
|
||||
function _processCollectWithReferral(
|
||||
@@ -196,6 +197,7 @@ contract LimitedFeeCollectModule is ICollectModule, FeeModuleBase, FollowValidat
|
||||
address recipient = _dataByPublicationByProfile[profileId][pubId].recipient;
|
||||
|
||||
IERC20(currency).safeTransferFrom(collector, recipient, adjustedAmount);
|
||||
IERC20(currency).safeTransferFrom(collector, treasury, treasuryAmount);
|
||||
if (treasuryAmount > 0)
|
||||
IERC20(currency).safeTransferFrom(collector, treasury, treasuryAmount);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ contract LimitedTimedFeeCollectModule is ICollectModule, FeeModuleBase, FollowVa
|
||||
!_currencyWhitelisted(currency) ||
|
||||
recipient == address(0) ||
|
||||
referralFee > BPS_MAX ||
|
||||
amount < BPS_MAX
|
||||
amount == 0
|
||||
) revert Errors.InitParamsInvalid();
|
||||
|
||||
_dataByPublicationByProfile[profileId][pubId].collectLimit = collectLimit;
|
||||
@@ -168,7 +168,8 @@ contract LimitedTimedFeeCollectModule is ICollectModule, FeeModuleBase, FollowVa
|
||||
uint256 adjustedAmount = amount - treasuryAmount;
|
||||
|
||||
IERC20(currency).safeTransferFrom(collector, recipient, adjustedAmount);
|
||||
IERC20(currency).safeTransferFrom(collector, treasury, treasuryAmount);
|
||||
if (treasuryAmount > 0)
|
||||
IERC20(currency).safeTransferFrom(collector, treasury, treasuryAmount);
|
||||
}
|
||||
|
||||
function _processCollectWithReferral(
|
||||
@@ -208,6 +209,7 @@ contract LimitedTimedFeeCollectModule is ICollectModule, FeeModuleBase, FollowVa
|
||||
address recipient = _dataByPublicationByProfile[profileId][pubId].recipient;
|
||||
|
||||
IERC20(currency).safeTransferFrom(collector, recipient, adjustedAmount);
|
||||
IERC20(currency).safeTransferFrom(collector, treasury, treasuryAmount);
|
||||
if (treasuryAmount > 0)
|
||||
IERC20(currency).safeTransferFrom(collector, treasury, treasuryAmount);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ contract TimedFeeCollectModule is ICollectModule, FeeModuleBase, FollowValidatio
|
||||
!_currencyWhitelisted(currency) ||
|
||||
recipient == address(0) ||
|
||||
referralFee > BPS_MAX ||
|
||||
amount < BPS_MAX
|
||||
amount == 0
|
||||
) revert Errors.InitParamsInvalid();
|
||||
|
||||
_dataByPublicationByProfile[profileId][pubId].amount = amount;
|
||||
@@ -155,7 +155,8 @@ contract TimedFeeCollectModule is ICollectModule, FeeModuleBase, FollowValidatio
|
||||
uint256 adjustedAmount = amount - treasuryAmount;
|
||||
|
||||
IERC20(currency).safeTransferFrom(collector, recipient, adjustedAmount);
|
||||
IERC20(currency).safeTransferFrom(collector, treasury, treasuryAmount);
|
||||
if (treasuryAmount > 0)
|
||||
IERC20(currency).safeTransferFrom(collector, treasury, treasuryAmount);
|
||||
}
|
||||
|
||||
function _processCollectWithReferral(
|
||||
@@ -195,6 +196,7 @@ contract TimedFeeCollectModule is ICollectModule, FeeModuleBase, FollowValidatio
|
||||
address recipient = _dataByPublicationByProfile[profileId][pubId].recipient;
|
||||
|
||||
IERC20(currency).safeTransferFrom(collector, recipient, adjustedAmount);
|
||||
IERC20(currency).safeTransferFrom(collector, treasury, treasuryAmount);
|
||||
if (treasuryAmount > 0)
|
||||
IERC20(currency).safeTransferFrom(collector, treasury, treasuryAmount);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ contract FeeFollowModule is IFollowModule, FeeModuleBase, FollowValidatorFollowM
|
||||
data,
|
||||
(uint256, address, address)
|
||||
);
|
||||
if (!_currencyWhitelisted(currency) || recipient == address(0) || amount < BPS_MAX)
|
||||
if (!_currencyWhitelisted(currency) || recipient == address(0) || amount == 0)
|
||||
revert Errors.InitParamsInvalid();
|
||||
|
||||
_dataByProfile[profileId].amount = amount;
|
||||
@@ -87,7 +87,8 @@ contract FeeFollowModule is IFollowModule, FeeModuleBase, FollowValidatorFollowM
|
||||
uint256 adjustedAmount = amount - treasuryAmount;
|
||||
|
||||
IERC20(currency).safeTransferFrom(follower, recipient, adjustedAmount);
|
||||
IERC20(currency).safeTransferFrom(follower, treasury, treasuryAmount);
|
||||
if (treasuryAmount > 0)
|
||||
IERC20(currency).safeTransferFrom(follower, treasury, treasuryAmount);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,7 +11,6 @@ import {IFollowNFT} from '../interfaces/IFollowNFT.sol';
|
||||
import {ICollectNFT} from '../interfaces/ICollectNFT.sol';
|
||||
import {IFollowModule} from '../interfaces/IFollowModule.sol';
|
||||
import {ICollectModule} from '../interfaces/ICollectModule.sol';
|
||||
import {ERC721Time} from '../core/base/ERC721Time.sol';
|
||||
import {Clones} from '@openzeppelin/contracts/proxy/Clones.sol';
|
||||
import {Strings} from '@openzeppelin/contracts/utils/Strings.sol';
|
||||
|
||||
@@ -35,6 +34,7 @@ library InteractionLogic {
|
||||
* @param followModuleDatas The array of follow module data parameters to pass to each profile's follow module.
|
||||
* @param followNFTImpl The address of the follow NFT implementation, which has to be passed because it's an immutable in the hub.
|
||||
* @param _profileById A pointer to the storage mapping of profile structs by profile ID.
|
||||
* @param _profileIdByHandleHash A pointer to the storage mapping of profile IDs by handle hash.
|
||||
*/
|
||||
function follow(
|
||||
address follower,
|
||||
@@ -47,7 +47,7 @@ library InteractionLogic {
|
||||
if (profileIds.length != followModuleDatas.length) revert Errors.ArrayMismatch();
|
||||
for (uint256 i = 0; i < profileIds.length; ++i) {
|
||||
string memory handle = _profileById[profileIds[i]].handle;
|
||||
if (_profileIdByHandleHash[keccak256(bytes(handle))] == 0)
|
||||
if (_profileIdByHandleHash[keccak256(bytes(handle))] != profileIds[i])
|
||||
revert Errors.TokenDoesNotExist();
|
||||
|
||||
address followModule = _profileById[profileIds[i]].followModule;
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -404,7 +404,10 @@ library PublishingLogic {
|
||||
if (
|
||||
(byteHandle[i] < '0' ||
|
||||
byteHandle[i] > 'z' ||
|
||||
(byteHandle[i] > '9' && byteHandle[i] < 'a')) && byteHandle[i] != '.'
|
||||
(byteHandle[i] > '9' && byteHandle[i] < 'a')) &&
|
||||
byteHandle[i] != '.' &&
|
||||
byteHandle[i] != '-' &&
|
||||
byteHandle[i] != '_'
|
||||
) revert Errors.HandleContainsInvalidCharacters();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@ version: '3.5'
|
||||
|
||||
services:
|
||||
contracts-env:
|
||||
security_opt:
|
||||
- no-new-privileges
|
||||
user: "${USERID?}:${USERID?}"
|
||||
env_file:
|
||||
- .env
|
||||
|
||||
@@ -222,14 +222,15 @@ task('full-deploy-verify', 'deploys the entire Lens Protocol with explorer verif
|
||||
[lensHub.address, moduleGlobals.address],
|
||||
'contracts/core/modules/follow/FeeFollowModule.sol:FeeFollowModule'
|
||||
);
|
||||
console.log('\n\t-- Deploying approvalFollowModule --');
|
||||
const approvalFollowModule = await deployWithVerify(
|
||||
new ApprovalFollowModule__factory(deployer).deploy(lensHub.address, {
|
||||
nonce: deployerNonce++,
|
||||
}),
|
||||
[lensHub.address],
|
||||
'contracts/core/modules/follow/ApprovalFollowModule.sol:ApprovalFollowModule'
|
||||
);
|
||||
// --- COMMENTED OUT AS THIS IS NOT A LAUNCH MODULE ---
|
||||
// console.log('\n\t-- Deploying approvalFollowModule --');
|
||||
// const approvalFollowModule = await deployWithVerify(
|
||||
// new ApprovalFollowModule__factory(deployer).deploy(lensHub.address, {
|
||||
// nonce: deployerNonce++,
|
||||
// }),
|
||||
// [lensHub.address],
|
||||
// 'contracts/core/modules/follow/ApprovalFollowModule.sol:ApprovalFollowModule'
|
||||
// );
|
||||
|
||||
// Deploy reference module
|
||||
console.log('\n\t-- Deploying followerOnlyReferenceModule --');
|
||||
@@ -276,11 +277,12 @@ task('full-deploy-verify', 'deploys the entire Lens Protocol with explorer verif
|
||||
await waitForTx(
|
||||
lensHub.whitelistFollowModule(feeFollowModule.address, true, { nonce: governanceNonce++ })
|
||||
);
|
||||
await waitForTx(
|
||||
lensHub.whitelistFollowModule(approvalFollowModule.address, true, {
|
||||
nonce: governanceNonce++,
|
||||
})
|
||||
);
|
||||
// --- COMMENTED OUT AS THIS IS NOT A LAUNCH MODULE ---
|
||||
// await waitForTx(
|
||||
// lensHub.whitelistFollowModule(approvalFollowModule.address, true, {
|
||||
// nonce: governanceNonce++,
|
||||
// })
|
||||
// );
|
||||
|
||||
// Whitelist the reference module
|
||||
console.log('\n\t-- Whitelisting Reference Module --');
|
||||
@@ -308,7 +310,8 @@ task('full-deploy-verify', 'deploys the entire Lens Protocol with explorer verif
|
||||
'revert collect module': revertCollectModule.address,
|
||||
'empty collect module': freeCollectModule.address,
|
||||
'fee follow module': feeFollowModule.address,
|
||||
'approval follow module': approvalFollowModule.address,
|
||||
// --- COMMENTED OUT AS THIS IS NOT A LAUNCH MODULE ---
|
||||
// 'approval follow module': approvalFollowModule.address,
|
||||
'follower only reference module': followerOnlyReferenceModule.address,
|
||||
};
|
||||
const json = JSON.stringify(addrs, null, 2);
|
||||
|
||||
@@ -175,10 +175,11 @@ task('full-deploy', 'deploys the entire Lens Protocol').setAction(async ({}, hre
|
||||
nonce: deployerNonce++,
|
||||
})
|
||||
);
|
||||
console.log('\n\t-- Deploying approvalFollowModule --');
|
||||
const approvalFollowModule = await deployContract(
|
||||
new ApprovalFollowModule__factory(deployer).deploy(lensHub.address, { nonce: deployerNonce++ })
|
||||
);
|
||||
// --- COMMENTED OUT AS THIS IS NOT A LAUNCH MODULE ---
|
||||
// console.log('\n\t-- Deploying approvalFollowModule --');
|
||||
// const approvalFollowModule = await deployContract(
|
||||
// new ApprovalFollowModule__factory(deployer).deploy(lensHub.address, { nonce: deployerNonce++ })
|
||||
// );
|
||||
|
||||
// Deploy reference module
|
||||
console.log('\n\t-- Deploying followerOnlyReferenceModule --');
|
||||
@@ -221,9 +222,10 @@ task('full-deploy', 'deploys the entire Lens Protocol').setAction(async ({}, hre
|
||||
await waitForTx(
|
||||
lensHub.whitelistFollowModule(feeFollowModule.address, true, { nonce: governanceNonce++ })
|
||||
);
|
||||
await waitForTx(
|
||||
lensHub.whitelistFollowModule(approvalFollowModule.address, true, { nonce: governanceNonce++ })
|
||||
);
|
||||
// --- COMMENTED OUT AS THIS IS NOT A LAUNCH MODULE ---
|
||||
// await waitForTx(
|
||||
// lensHub.whitelistFollowModule(approvalFollowModule.address, true, { nonce: governanceNonce++ })
|
||||
// );
|
||||
|
||||
// Whitelist the reference module
|
||||
console.log('\n\t-- Whitelisting Reference Module --');
|
||||
@@ -259,7 +261,8 @@ task('full-deploy', 'deploys the entire Lens Protocol').setAction(async ({}, hre
|
||||
'revert collect module': revertCollectModule.address,
|
||||
'empty collect module': freeCollectModule.address,
|
||||
'fee follow module': feeFollowModule.address,
|
||||
'approval follow module': approvalFollowModule.address,
|
||||
// --- COMMENTED OUT AS THIS IS NOT A LAUNCH MODULE ---
|
||||
// 'approval follow module': approvalFollowModule.address,
|
||||
'follower only reference module': followerOnlyReferenceModule.address,
|
||||
};
|
||||
const json = JSON.stringify(addrs, null, 2);
|
||||
|
||||
@@ -15,6 +15,8 @@ import { hexlify, keccak256, RLP, toUtf8Bytes } from 'ethers/lib/utils';
|
||||
import { LensHub__factory } from '../../typechain-types';
|
||||
import { TransactionReceipt, TransactionResponse } from '@ethersproject/providers';
|
||||
import hre, { ethers } from 'hardhat';
|
||||
import { readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
export enum ProtocolState {
|
||||
Unpaused,
|
||||
@@ -26,16 +28,17 @@ export function matchEvent(
|
||||
receipt: TransactionReceipt,
|
||||
name: string,
|
||||
expectedArgs?: any[],
|
||||
eventContract: Contract = eventsLib
|
||||
eventContract: Contract = eventsLib,
|
||||
emitterAddress?: string
|
||||
) {
|
||||
const events = receipt.logs;
|
||||
|
||||
if (events != undefined) {
|
||||
// match name from list of events in eventContract, when found, compute the sigHash
|
||||
let sigHash: string | undefined;
|
||||
for (let contractEvents of Object.keys(eventContract.interface.events)) {
|
||||
if (contractEvents.startsWith(name) && contractEvents.charAt(name.length) == '(') {
|
||||
sigHash = keccak256(toUtf8Bytes(contractEvents));
|
||||
for (let contractEvent of Object.keys(eventContract.interface.events)) {
|
||||
if (contractEvent.startsWith(name) && contractEvent.charAt(name.length) == '(') {
|
||||
sigHash = keccak256(toUtf8Bytes(contractEvent));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -51,6 +54,10 @@ export function matchEvent(
|
||||
for (let emittedEvent of events) {
|
||||
// If we find one with the correct sighash, check if it is the one we're looking for
|
||||
if (emittedEvent.topics[0] == sigHash) {
|
||||
// If an emitter address is passed, validate that this is indeed the correct emitter, if not, continue
|
||||
if (emitterAddress) {
|
||||
if (emittedEvent.address != emitterAddress) continue;
|
||||
}
|
||||
const event = eventContract.interface.parseLog(emittedEvent);
|
||||
// If there are expected arguments, validate them, otherwise, return here
|
||||
if (expectedArgs) {
|
||||
@@ -106,7 +113,9 @@ export function matchEvent(
|
||||
if (invalidParamsButExists) {
|
||||
logger.throwError(`Event "${name}" found in logs but with unexpected args`);
|
||||
} else {
|
||||
logger.throwError(`Event "${name}" not found in given transaction log`);
|
||||
logger.throwError(
|
||||
`Event "${name}" not found emitted by "${emitterAddress}" in given transaction log`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
logger.throwError('No events were emitted');
|
||||
@@ -447,7 +456,21 @@ export async function getCollectWithSigParts(
|
||||
return await getSig(msgParams);
|
||||
}
|
||||
|
||||
export async function getJsonMetadataFromBase64TokenUri(tokenUri: string) {
|
||||
export interface TokenUriMetadataAttribute {
|
||||
trait_type: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface ProfileTokenUriMetadata {
|
||||
name: string;
|
||||
description: string;
|
||||
image: string;
|
||||
attributes: TokenUriMetadataAttribute[];
|
||||
}
|
||||
|
||||
export async function getMetadataFromBase64TokenUri(
|
||||
tokenUri: string
|
||||
): Promise<ProfileTokenUriMetadata> {
|
||||
const splittedTokenUri = tokenUri.split('data:application/json;base64,');
|
||||
if (splittedTokenUri.length != 2) {
|
||||
logger.throwError('Wrong or unrecognized token URI format');
|
||||
@@ -459,6 +482,19 @@ export async function getJsonMetadataFromBase64TokenUri(tokenUri: string) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function getDecodedSvgImage(tokenUriMetadata: ProfileTokenUriMetadata) {
|
||||
const splittedImage = tokenUriMetadata.image.split('data:image/svg+xml;base64,');
|
||||
if (splittedImage.length != 2) {
|
||||
logger.throwError('Wrong or unrecognized token URI format');
|
||||
} else {
|
||||
return ethers.utils.toUtf8String(ethers.utils.base64.decode(splittedImage[1]));
|
||||
}
|
||||
}
|
||||
|
||||
export function loadTestResourceAsUtf8String(relativePathToResouceDir: string) {
|
||||
return readFileSync(join('test', 'resources', relativePathToResouceDir), 'utf8');
|
||||
}
|
||||
|
||||
// Modified from AaveTokenV2 repo
|
||||
const buildPermitParams = (
|
||||
nft: string,
|
||||
|
||||
@@ -97,6 +97,24 @@ makeSuiteCleanRoom('Multi-State Hub', function () {
|
||||
|
||||
context('Paused State', function () {
|
||||
context('Scenarios', async function () {
|
||||
it('User should create a profile, governance should pause the hub, transferring the profile should fail', async function () {
|
||||
await expect(
|
||||
lensHub.createProfile({
|
||||
to: userAddress,
|
||||
handle: MOCK_PROFILE_HANDLE,
|
||||
imageURI: MOCK_PROFILE_URI,
|
||||
followModule: ZERO_ADDRESS,
|
||||
followModuleData: [],
|
||||
followNFTURI: MOCK_FOLLOW_NFT_URI,
|
||||
})
|
||||
).to.not.be.reverted;
|
||||
|
||||
await expect(lensHub.connect(governance).setState(ProtocolState.Paused)).to.not.be.reverted;
|
||||
|
||||
await expect(
|
||||
lensHub.transferFrom(userAddress, userTwoAddress, FIRST_PROFILE_ID)
|
||||
).to.be.revertedWith(ERRORS.PAUSED);
|
||||
});
|
||||
it('Governance should pause the hub, profile creation should fail, then governance unpauses the hub and profile creation should work', async function () {
|
||||
await expect(lensHub.connect(governance).setState(ProtocolState.Paused)).to.not.be.reverted;
|
||||
|
||||
@@ -472,7 +490,7 @@ makeSuiteCleanRoom('Multi-State Hub', function () {
|
||||
referenceModule: ZERO_ADDRESS,
|
||||
referenceModuleData: [],
|
||||
})
|
||||
).to.be.revertedWith(ERRORS.PAUSED);
|
||||
).to.be.revertedWith(ERRORS.PUBLISHING_PAUSED);
|
||||
|
||||
await expect(
|
||||
lensHub.connect(governance).setState(ProtocolState.Unpaused)
|
||||
@@ -538,7 +556,7 @@ makeSuiteCleanRoom('Multi-State Hub', function () {
|
||||
deadline: MAX_UINT256,
|
||||
},
|
||||
})
|
||||
).to.be.revertedWith(ERRORS.PAUSED);
|
||||
).to.be.revertedWith(ERRORS.PUBLISHING_PAUSED);
|
||||
|
||||
await expect(
|
||||
lensHub.connect(governance).setState(ProtocolState.Unpaused)
|
||||
@@ -602,7 +620,7 @@ makeSuiteCleanRoom('Multi-State Hub', function () {
|
||||
referenceModule: ZERO_ADDRESS,
|
||||
referenceModuleData: [],
|
||||
})
|
||||
).to.be.revertedWith(ERRORS.PAUSED);
|
||||
).to.be.revertedWith(ERRORS.PUBLISHING_PAUSED);
|
||||
|
||||
await expect(
|
||||
lensHub.connect(governance).setState(ProtocolState.Unpaused)
|
||||
@@ -685,7 +703,7 @@ makeSuiteCleanRoom('Multi-State Hub', function () {
|
||||
deadline: MAX_UINT256,
|
||||
},
|
||||
})
|
||||
).to.be.revertedWith(ERRORS.PAUSED);
|
||||
).to.be.revertedWith(ERRORS.PUBLISHING_PAUSED);
|
||||
|
||||
await expect(
|
||||
lensHub.connect(governance).setState(ProtocolState.Unpaused)
|
||||
@@ -748,7 +766,7 @@ makeSuiteCleanRoom('Multi-State Hub', function () {
|
||||
referenceModule: ZERO_ADDRESS,
|
||||
referenceModuleData: [],
|
||||
})
|
||||
).to.be.revertedWith(ERRORS.PAUSED);
|
||||
).to.be.revertedWith(ERRORS.PUBLISHING_PAUSED);
|
||||
|
||||
await expect(
|
||||
lensHub.connect(governance).setState(ProtocolState.Unpaused)
|
||||
@@ -821,7 +839,7 @@ makeSuiteCleanRoom('Multi-State Hub', function () {
|
||||
deadline: MAX_UINT256,
|
||||
},
|
||||
})
|
||||
).to.be.revertedWith(ERRORS.PAUSED);
|
||||
).to.be.revertedWith(ERRORS.PUBLISHING_PAUSED);
|
||||
|
||||
await expect(
|
||||
lensHub.connect(governance).setState(ProtocolState.Unpaused)
|
||||
|
||||
@@ -184,6 +184,19 @@ makeSuiteCleanRoom('Profile Creation', function () {
|
||||
expect(tokenData.mintTimestamp).to.eq(timestamp);
|
||||
});
|
||||
|
||||
it('User should be able to create a profile with a handle including "-" and "_" characters', async function () {
|
||||
await expect(
|
||||
lensHub.createProfile({
|
||||
to: userAddress,
|
||||
handle: 'morse--__-_--code',
|
||||
imageURI: MOCK_PROFILE_URI,
|
||||
followModule: ZERO_ADDRESS,
|
||||
followModuleData: [],
|
||||
followNFTURI: MOCK_FOLLOW_NFT_URI,
|
||||
})
|
||||
).to.not.be.reverted;
|
||||
});
|
||||
|
||||
it('User should be able to create a profile with a handle 16 bytes long, then fail to create with the same handle, and create again with a different handle', async function () {
|
||||
await expect(
|
||||
lensHub.createProfile({
|
||||
|
||||
@@ -6,9 +6,11 @@ import { MAX_UINT256, ZERO_ADDRESS } from '../../helpers/constants';
|
||||
import { ERRORS } from '../../helpers/errors';
|
||||
import {
|
||||
cancelWithPermitForAll,
|
||||
getJsonMetadataFromBase64TokenUri,
|
||||
getDecodedSvgImage,
|
||||
getMetadataFromBase64TokenUri,
|
||||
getSetFollowNFTURIWithSigParts,
|
||||
getSetProfileImageURIWithSigParts,
|
||||
loadTestResourceAsUtf8String,
|
||||
} from '../../helpers/utils';
|
||||
import {
|
||||
FIRST_PROFILE_ID,
|
||||
@@ -67,132 +69,130 @@ makeSuiteCleanRoom('Profile URI Functionality', function () {
|
||||
context('Scenarios', function () {
|
||||
it('User should have a custom picture tokenURI after setting the profile imageURI', async function () {
|
||||
await expect(lensHub.setProfileImageURI(FIRST_PROFILE_ID, MOCK_URI)).to.not.be.reverted;
|
||||
const tokenURI = await lensHub.tokenURI(FIRST_PROFILE_ID);
|
||||
const jsonMetadata = await getJsonMetadataFromBase64TokenUri(tokenURI);
|
||||
expect(jsonMetadata.name).to.eq(`@${MOCK_PROFILE_HANDLE}`);
|
||||
expect(jsonMetadata.description).to.eq(`@${MOCK_PROFILE_HANDLE} - Lens profile`);
|
||||
const tokenUri = await lensHub.tokenURI(FIRST_PROFILE_ID);
|
||||
const metadata = await getMetadataFromBase64TokenUri(tokenUri);
|
||||
expect(metadata.name).to.eq(`@${MOCK_PROFILE_HANDLE}`);
|
||||
expect(metadata.description).to.eq(`@${MOCK_PROFILE_HANDLE} - Lens profile`);
|
||||
const expectedAttributes = [
|
||||
{ trait_type: 'id', value: `#${FIRST_PROFILE_ID.toString()}` },
|
||||
{ trait_type: 'followers', value: '0' },
|
||||
{ trait_type: 'owner', value: userAddress.toLowerCase() },
|
||||
{ trait_type: 'handle', value: `@${MOCK_PROFILE_HANDLE}` },
|
||||
];
|
||||
expect(jsonMetadata.attributes).to.eql(expectedAttributes);
|
||||
expect(keccak256(toUtf8Bytes(tokenURI))).to.eq(
|
||||
'0xe1731460db6ce5fa9f3a9f2bd778a8af49e623dceb531df6b1a5c162b7c2d79a'
|
||||
);
|
||||
expect(metadata.attributes).to.eql(expectedAttributes);
|
||||
const actualSvg = await getDecodedSvgImage(metadata);
|
||||
const expectedSvg = loadTestResourceAsUtf8String('profile-token-uri-images/mock.svg');
|
||||
expect(actualSvg).to.eq(expectedSvg);
|
||||
});
|
||||
|
||||
it('Default image should be used when no imageURI set', async function () {
|
||||
await expect(lensHub.setProfileImageURI(FIRST_PROFILE_ID, '')).to.not.be.reverted;
|
||||
const tokenURI = await lensHub.tokenURI(FIRST_PROFILE_ID);
|
||||
const jsonMetadata = await getJsonMetadataFromBase64TokenUri(tokenURI);
|
||||
expect(jsonMetadata.name).to.eq(`@${MOCK_PROFILE_HANDLE}`);
|
||||
expect(jsonMetadata.description).to.eq(`@${MOCK_PROFILE_HANDLE} - Lens profile`);
|
||||
const tokenUri = await lensHub.tokenURI(FIRST_PROFILE_ID);
|
||||
const metadata = await getMetadataFromBase64TokenUri(tokenUri);
|
||||
expect(metadata.name).to.eq(`@${MOCK_PROFILE_HANDLE}`);
|
||||
expect(metadata.description).to.eq(`@${MOCK_PROFILE_HANDLE} - Lens profile`);
|
||||
const expectedAttributes = [
|
||||
{ trait_type: 'id', value: `#${FIRST_PROFILE_ID.toString()}` },
|
||||
{ trait_type: 'followers', value: '0' },
|
||||
{ trait_type: 'owner', value: userAddress.toLowerCase() },
|
||||
{ trait_type: 'handle', value: `@${MOCK_PROFILE_HANDLE}` },
|
||||
];
|
||||
expect(jsonMetadata.attributes).to.eql(expectedAttributes);
|
||||
expect(keccak256(toUtf8Bytes(tokenURI))).to.eq(
|
||||
'0xa21f2a3aa9300a248d3a7acd3f4ff309291653121df87ffe3be545fa1dbd65e5'
|
||||
);
|
||||
expect(metadata.attributes).to.eql(expectedAttributes);
|
||||
const actualSvg = await getDecodedSvgImage(metadata);
|
||||
const expectedSvg = loadTestResourceAsUtf8String('profile-token-uri-images/default.svg');
|
||||
expect(actualSvg).to.eq(expectedSvg);
|
||||
});
|
||||
|
||||
it('Default image should be used when imageURI contains double-quotes', async function () {
|
||||
const imageURI =
|
||||
const imageUri =
|
||||
'https://ipfs.io/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrztRLMiMPL8wBuTGsMnR" <rect x="10" y="10" fill="red';
|
||||
await expect(lensHub.setProfileImageURI(FIRST_PROFILE_ID, imageURI)).to.not.be.reverted;
|
||||
const tokenURI = await lensHub.tokenURI(FIRST_PROFILE_ID);
|
||||
const jsonMetadata = await getJsonMetadataFromBase64TokenUri(tokenURI);
|
||||
expect(jsonMetadata.name).to.eq(`@${MOCK_PROFILE_HANDLE}`);
|
||||
expect(jsonMetadata.description).to.eq(`@${MOCK_PROFILE_HANDLE} - Lens profile`);
|
||||
await expect(lensHub.setProfileImageURI(FIRST_PROFILE_ID, imageUri)).to.not.be.reverted;
|
||||
const tokenUri = await lensHub.tokenURI(FIRST_PROFILE_ID);
|
||||
const metadata = await getMetadataFromBase64TokenUri(tokenUri);
|
||||
expect(metadata.name).to.eq(`@${MOCK_PROFILE_HANDLE}`);
|
||||
expect(metadata.description).to.eq(`@${MOCK_PROFILE_HANDLE} - Lens profile`);
|
||||
const expectedAttributes = [
|
||||
{ trait_type: 'id', value: `#${FIRST_PROFILE_ID.toString()}` },
|
||||
{ trait_type: 'followers', value: '0' },
|
||||
{ trait_type: 'owner', value: userAddress.toLowerCase() },
|
||||
{ trait_type: 'handle', value: `@${MOCK_PROFILE_HANDLE}` },
|
||||
];
|
||||
expect(jsonMetadata.attributes).to.eql(expectedAttributes);
|
||||
expect(keccak256(toUtf8Bytes(tokenURI))).to.eq(
|
||||
'0xa21f2a3aa9300a248d3a7acd3f4ff309291653121df87ffe3be545fa1dbd65e5'
|
||||
);
|
||||
expect(metadata.attributes).to.eql(expectedAttributes);
|
||||
const actualSvg = await getDecodedSvgImage(metadata);
|
||||
const expectedSvg = loadTestResourceAsUtf8String('profile-token-uri-images/default.svg');
|
||||
expect(actualSvg).to.eq(expectedSvg);
|
||||
});
|
||||
|
||||
it('Should return the correct tokenURI after transfer', async function () {
|
||||
const tokenURIBeforeTransfer = await lensHub.tokenURI(FIRST_PROFILE_ID);
|
||||
const jsonMetadataBeforeTransfer = await getJsonMetadataFromBase64TokenUri(
|
||||
tokenURIBeforeTransfer
|
||||
);
|
||||
expect(jsonMetadataBeforeTransfer.name).to.eq(`@${MOCK_PROFILE_HANDLE}`);
|
||||
expect(jsonMetadataBeforeTransfer.description).to.eq(
|
||||
`@${MOCK_PROFILE_HANDLE} - Lens profile`
|
||||
);
|
||||
const tokenUriBeforeTransfer = await lensHub.tokenURI(FIRST_PROFILE_ID);
|
||||
const metadataBeforeTransfer = await getMetadataFromBase64TokenUri(tokenUriBeforeTransfer);
|
||||
expect(metadataBeforeTransfer.name).to.eq(`@${MOCK_PROFILE_HANDLE}`);
|
||||
expect(metadataBeforeTransfer.description).to.eq(`@${MOCK_PROFILE_HANDLE} - Lens profile`);
|
||||
const expectedAttributesBeforeTransfer = [
|
||||
{ trait_type: 'id', value: `#${FIRST_PROFILE_ID.toString()}` },
|
||||
{ trait_type: 'followers', value: '0' },
|
||||
{ trait_type: 'owner', value: userAddress.toLowerCase() },
|
||||
{ trait_type: 'handle', value: `@${MOCK_PROFILE_HANDLE}` },
|
||||
];
|
||||
expect(jsonMetadataBeforeTransfer.attributes).to.eql(expectedAttributesBeforeTransfer);
|
||||
expect(metadataBeforeTransfer.attributes).to.eql(expectedAttributesBeforeTransfer);
|
||||
const svgBeforeTransfer = await getDecodedSvgImage(metadataBeforeTransfer);
|
||||
const expectedSvg = loadTestResourceAsUtf8String(
|
||||
'profile-token-uri-images/mock-profile.svg'
|
||||
);
|
||||
expect(svgBeforeTransfer).to.eq(expectedSvg);
|
||||
|
||||
await expect(
|
||||
lensHub.transferFrom(userAddress, userTwoAddress, FIRST_PROFILE_ID)
|
||||
).to.not.be.reverted;
|
||||
|
||||
const tokenURIAfterTransfer = await lensHub.tokenURI(FIRST_PROFILE_ID);
|
||||
const jsonMetadataAfterTransfer = await getJsonMetadataFromBase64TokenUri(
|
||||
tokenURIAfterTransfer
|
||||
);
|
||||
expect(jsonMetadataAfterTransfer.name).to.eq(`@${MOCK_PROFILE_HANDLE}`);
|
||||
expect(jsonMetadataAfterTransfer.description).to.eq(
|
||||
`@${MOCK_PROFILE_HANDLE} - Lens profile`
|
||||
);
|
||||
const tokenUriAfterTransfer = await lensHub.tokenURI(FIRST_PROFILE_ID);
|
||||
const metadataAfterTransfer = await getMetadataFromBase64TokenUri(tokenUriAfterTransfer);
|
||||
expect(metadataAfterTransfer.name).to.eq(`@${MOCK_PROFILE_HANDLE}`);
|
||||
expect(metadataAfterTransfer.description).to.eq(`@${MOCK_PROFILE_HANDLE} - Lens profile`);
|
||||
const expectedAttributesAfterTransfer = [
|
||||
{ trait_type: 'id', value: `#${FIRST_PROFILE_ID.toString()}` },
|
||||
{ trait_type: 'followers', value: '0' },
|
||||
{ trait_type: 'owner', value: userTwoAddress.toLowerCase() },
|
||||
{ trait_type: 'handle', value: `@${MOCK_PROFILE_HANDLE}` },
|
||||
];
|
||||
expect(jsonMetadataAfterTransfer.attributes).to.eql(expectedAttributesAfterTransfer);
|
||||
expect(metadataAfterTransfer.attributes).to.eql(expectedAttributesAfterTransfer);
|
||||
const svgAfterTransfer = await getDecodedSvgImage(metadataAfterTransfer);
|
||||
expect(svgAfterTransfer).to.eq(expectedSvg);
|
||||
});
|
||||
|
||||
it('Should return the correct tokenURI after a follow', async function () {
|
||||
const tokenURIBeforeTransfer = await lensHub.tokenURI(FIRST_PROFILE_ID);
|
||||
const jsonMetadataBeforeTransfer = await getJsonMetadataFromBase64TokenUri(
|
||||
tokenURIBeforeTransfer
|
||||
);
|
||||
expect(jsonMetadataBeforeTransfer.name).to.eq(`@${MOCK_PROFILE_HANDLE}`);
|
||||
expect(jsonMetadataBeforeTransfer.description).to.eq(
|
||||
`@${MOCK_PROFILE_HANDLE} - Lens profile`
|
||||
);
|
||||
const expectedAttributesBeforeTransfer = [
|
||||
const tokenUriBeforeFollow = await lensHub.tokenURI(FIRST_PROFILE_ID);
|
||||
const metadataBeforeFollow = await getMetadataFromBase64TokenUri(tokenUriBeforeFollow);
|
||||
expect(metadataBeforeFollow.name).to.eq(`@${MOCK_PROFILE_HANDLE}`);
|
||||
expect(metadataBeforeFollow.description).to.eq(`@${MOCK_PROFILE_HANDLE} - Lens profile`);
|
||||
const expectedAttributesBeforeFollow = [
|
||||
{ trait_type: 'id', value: `#${FIRST_PROFILE_ID.toString()}` },
|
||||
{ trait_type: 'followers', value: '0' },
|
||||
{ trait_type: 'owner', value: userAddress.toLowerCase() },
|
||||
{ trait_type: 'handle', value: `@${MOCK_PROFILE_HANDLE}` },
|
||||
];
|
||||
expect(jsonMetadataBeforeTransfer.attributes).to.eql(expectedAttributesBeforeTransfer);
|
||||
expect(metadataBeforeFollow.attributes).to.eql(expectedAttributesBeforeFollow);
|
||||
const svgBeforeFollow = await getDecodedSvgImage(metadataBeforeFollow);
|
||||
const expectedSvg = loadTestResourceAsUtf8String(
|
||||
'profile-token-uri-images/mock-profile.svg'
|
||||
);
|
||||
expect(svgBeforeFollow).to.eq(expectedSvg);
|
||||
|
||||
await expect(lensHub.follow([FIRST_PROFILE_ID], [[]])).to.not.be.reverted;
|
||||
|
||||
const tokenURIAfterTransfer = await lensHub.tokenURI(FIRST_PROFILE_ID);
|
||||
const jsonMetadataAfterTransfer = await getJsonMetadataFromBase64TokenUri(
|
||||
tokenURIAfterTransfer
|
||||
);
|
||||
expect(jsonMetadataAfterTransfer.name).to.eq(`@${MOCK_PROFILE_HANDLE}`);
|
||||
expect(jsonMetadataAfterTransfer.description).to.eq(
|
||||
`@${MOCK_PROFILE_HANDLE} - Lens profile`
|
||||
);
|
||||
const expectedAttributesAfterTransfer = [
|
||||
const tokenUriAfterFollow = await lensHub.tokenURI(FIRST_PROFILE_ID);
|
||||
const metadataAfterFollow = await getMetadataFromBase64TokenUri(tokenUriAfterFollow);
|
||||
expect(metadataAfterFollow.name).to.eq(`@${MOCK_PROFILE_HANDLE}`);
|
||||
expect(metadataAfterFollow.description).to.eq(`@${MOCK_PROFILE_HANDLE} - Lens profile`);
|
||||
const expectedAttributesAfterFollow = [
|
||||
{ trait_type: 'id', value: `#${FIRST_PROFILE_ID.toString()}` },
|
||||
{ trait_type: 'followers', value: '1' },
|
||||
{ trait_type: 'owner', value: userAddress.toLowerCase() },
|
||||
{ trait_type: 'handle', value: `@${MOCK_PROFILE_HANDLE}` },
|
||||
];
|
||||
expect(jsonMetadataAfterTransfer.attributes).to.eql(expectedAttributesAfterTransfer);
|
||||
expect(metadataAfterFollow.attributes).to.eql(expectedAttributesAfterFollow);
|
||||
const svgAfterFollow = await getDecodedSvgImage(metadataAfterFollow);
|
||||
expect(svgAfterFollow).to.eq(expectedSvg);
|
||||
});
|
||||
|
||||
it('User should set user two as a dispatcher on their profile, user two should set the profile URI', async function () {
|
||||
@@ -200,10 +200,20 @@ makeSuiteCleanRoom('Profile URI Functionality', function () {
|
||||
await expect(
|
||||
lensHub.connect(userTwo).setProfileImageURI(FIRST_PROFILE_ID, MOCK_URI)
|
||||
).to.not.be.reverted;
|
||||
const tokenURI = await lensHub.tokenURI(FIRST_PROFILE_ID);
|
||||
expect(keccak256(toUtf8Bytes(tokenURI))).to.eq(
|
||||
'0xe1731460db6ce5fa9f3a9f2bd778a8af49e623dceb531df6b1a5c162b7c2d79a'
|
||||
);
|
||||
const tokenUri = await lensHub.tokenURI(FIRST_PROFILE_ID);
|
||||
const metadata = await getMetadataFromBase64TokenUri(tokenUri);
|
||||
expect(metadata.name).to.eq(`@${MOCK_PROFILE_HANDLE}`);
|
||||
expect(metadata.description).to.eq(`@${MOCK_PROFILE_HANDLE} - Lens profile`);
|
||||
const expectedAttributes = [
|
||||
{ trait_type: 'id', value: `#${FIRST_PROFILE_ID.toString()}` },
|
||||
{ trait_type: 'followers', value: '0' },
|
||||
{ trait_type: 'owner', value: userAddress.toLowerCase() },
|
||||
{ trait_type: 'handle', value: `@${MOCK_PROFILE_HANDLE}` },
|
||||
];
|
||||
expect(metadata.attributes).to.eql(expectedAttributes);
|
||||
const actualSvg = await getDecodedSvgImage(metadata);
|
||||
const expectedSvg = loadTestResourceAsUtf8String('profile-token-uri-images/mock.svg');
|
||||
expect(actualSvg).to.eq(expectedSvg);
|
||||
});
|
||||
|
||||
it('User should follow profile 1, user should change the follow NFT URI, URI is accurate before and after the change', async function () {
|
||||
@@ -436,11 +446,22 @@ makeSuiteCleanRoom('Profile URI Functionality', function () {
|
||||
MAX_UINT256
|
||||
);
|
||||
|
||||
const tokenURIBefore = await lensHub.tokenURI(FIRST_PROFILE_ID);
|
||||
|
||||
expect(keccak256(toUtf8Bytes(tokenURIBefore))).to.eq(
|
||||
'0x6ed04aa8ab68b7c5201afb2f9655d8fd483559794ce933b7f2282549ca9e3dba'
|
||||
const tokenUriBefore = await lensHub.tokenURI(FIRST_PROFILE_ID);
|
||||
const metadataBefore = await getMetadataFromBase64TokenUri(tokenUriBefore);
|
||||
expect(metadataBefore.name).to.eq(`@${MOCK_PROFILE_HANDLE}`);
|
||||
expect(metadataBefore.description).to.eq(`@${MOCK_PROFILE_HANDLE} - Lens profile`);
|
||||
const expectedAttributesBefore = [
|
||||
{ trait_type: 'id', value: `#${FIRST_PROFILE_ID.toString()}` },
|
||||
{ trait_type: 'followers', value: '0' },
|
||||
{ trait_type: 'owner', value: testWallet.address.toLowerCase() },
|
||||
{ trait_type: 'handle', value: `@${MOCK_PROFILE_HANDLE}` },
|
||||
];
|
||||
expect(metadataBefore.attributes).to.eql(expectedAttributesBefore);
|
||||
const svgBefore = await getDecodedSvgImage(metadataBefore);
|
||||
const expectedSvgBefore = loadTestResourceAsUtf8String(
|
||||
'profile-token-uri-images/mock-profile.svg'
|
||||
);
|
||||
expect(svgBefore).to.eq(expectedSvgBefore);
|
||||
|
||||
await expect(
|
||||
lensHub.setProfileImageURIWithSig({
|
||||
@@ -455,14 +476,24 @@ makeSuiteCleanRoom('Profile URI Functionality', function () {
|
||||
})
|
||||
).to.not.be.reverted;
|
||||
|
||||
const tokenURIAfter = await lensHub.tokenURI(FIRST_PROFILE_ID);
|
||||
const tokenUriAfter = await lensHub.tokenURI(FIRST_PROFILE_ID);
|
||||
|
||||
expect(MOCK_PROFILE_URI).to.not.eq(MOCK_URI);
|
||||
expect(tokenURIBefore).to.not.eq(tokenURIAfter);
|
||||
expect(tokenUriBefore).to.not.eq(tokenUriAfter);
|
||||
|
||||
expect(keccak256(toUtf8Bytes(tokenURIAfter))).to.eq(
|
||||
'0x2f93fe42168b386790c5615061bcd3c1d8aac1976bddca8cf57eb2bc525541ab'
|
||||
);
|
||||
const metadataAfter = await getMetadataFromBase64TokenUri(tokenUriAfter);
|
||||
expect(metadataAfter.name).to.eq(`@${MOCK_PROFILE_HANDLE}`);
|
||||
expect(metadataAfter.description).to.eq(`@${MOCK_PROFILE_HANDLE} - Lens profile`);
|
||||
const expectedAttributesAfter = [
|
||||
{ trait_type: 'id', value: `#${FIRST_PROFILE_ID.toString()}` },
|
||||
{ trait_type: 'followers', value: '0' },
|
||||
{ trait_type: 'owner', value: testWallet.address.toLowerCase() },
|
||||
{ trait_type: 'handle', value: `@${MOCK_PROFILE_HANDLE}` },
|
||||
];
|
||||
expect(metadataAfter.attributes).to.eql(expectedAttributesAfter);
|
||||
const svgAfter = await getDecodedSvgImage(metadataAfter);
|
||||
const expectedSvgAfter = loadTestResourceAsUtf8String('profile-token-uri-images/mock.svg');
|
||||
expect(svgAfter).to.eq(expectedSvgAfter);
|
||||
});
|
||||
|
||||
it('TestWallet should set the follow NFT URI with sig', async function () {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { BigNumber } from '@ethersproject/contracts/node_modules/@ethersproject/
|
||||
import { parseEther } from '@ethersproject/units';
|
||||
import '@nomiclabs/hardhat-ethers';
|
||||
import { expect } from 'chai';
|
||||
import { ERC20__factory } from '../../../typechain-types';
|
||||
import { MAX_UINT256, ZERO_ADDRESS } from '../../helpers/constants';
|
||||
import { ERRORS } from '../../helpers/errors';
|
||||
import { getTimestamp, matchEvent, waitForTx } from '../../helpers/utils';
|
||||
@@ -13,6 +14,7 @@ import {
|
||||
FIRST_PROFILE_ID,
|
||||
governance,
|
||||
lensHub,
|
||||
lensHubImpl,
|
||||
makeSuiteCleanRoom,
|
||||
MOCK_FOLLOW_NFT_URI,
|
||||
MOCK_PROFILE_HANDLE,
|
||||
@@ -22,6 +24,7 @@ import {
|
||||
REFERRAL_FEE_BPS,
|
||||
treasuryAddress,
|
||||
TREASURY_FEE_BPS,
|
||||
user,
|
||||
userAddress,
|
||||
userTwo,
|
||||
userTwoAddress,
|
||||
@@ -102,7 +105,7 @@ makeSuiteCleanRoom('Fee Collect Module', function () {
|
||||
).to.be.revertedWith(ERRORS.INIT_PARAMS_INVALID);
|
||||
});
|
||||
|
||||
it('user should fail to post with fee collect module using amount lower than max BPS', async function () {
|
||||
it('user should fail to post with fee collect module using zero amount', async function () {
|
||||
const collectModuleData = abiCoder.encode(
|
||||
['uint256', 'address', 'address', 'uint16', 'bool'],
|
||||
[9999, currency.address, userAddress, REFERRAL_FEE_BPS, true]
|
||||
@@ -138,6 +141,105 @@ makeSuiteCleanRoom('Fee Collect Module', function () {
|
||||
).to.not.be.reverted;
|
||||
});
|
||||
|
||||
it('Governance should set the treasury fee BPS to zero, userTwo collecting should not emit a transfer event to the treasury', async function () {
|
||||
await expect(moduleGlobals.connect(governance).setTreasuryFee(0)).to.not.be.reverted;
|
||||
const data = abiCoder.encode(
|
||||
['address', 'uint256'],
|
||||
[currency.address, DEFAULT_COLLECT_PRICE]
|
||||
);
|
||||
await expect(lensHub.connect(userTwo).follow([FIRST_PROFILE_ID], [[]])).to.not.be.reverted;
|
||||
|
||||
await expect(currency.mint(userTwoAddress, MAX_UINT256)).to.not.be.reverted;
|
||||
await expect(
|
||||
currency.connect(userTwo).approve(feeCollectModule.address, MAX_UINT256)
|
||||
).to.not.be.reverted;
|
||||
|
||||
const tx = lensHub.connect(userTwo).collect(FIRST_PROFILE_ID, 1, data);
|
||||
const receipt = await waitForTx(tx);
|
||||
|
||||
let currencyEventCount = 0;
|
||||
for (let log of receipt.logs) {
|
||||
if (log.address == currency.address) {
|
||||
currencyEventCount++;
|
||||
}
|
||||
}
|
||||
expect(currencyEventCount).to.eq(1);
|
||||
matchEvent(
|
||||
receipt,
|
||||
'Transfer',
|
||||
[userTwoAddress, userAddress, DEFAULT_COLLECT_PRICE],
|
||||
currency,
|
||||
currency.address
|
||||
);
|
||||
});
|
||||
|
||||
it('UserTwo should mirror the original post, governance should set the treasury fee BPS to zero, userTwo collecting their mirror should not emit a transfer event to the treasury', async function () {
|
||||
const secondProfileId = FIRST_PROFILE_ID + 1;
|
||||
await expect(
|
||||
lensHub.connect(userTwo).createProfile({
|
||||
to: userTwoAddress,
|
||||
handle: 'usertwo',
|
||||
imageURI: MOCK_PROFILE_URI,
|
||||
followModule: ZERO_ADDRESS,
|
||||
followModuleData: [],
|
||||
followNFTURI: MOCK_FOLLOW_NFT_URI,
|
||||
})
|
||||
).to.not.be.reverted;
|
||||
await expect(
|
||||
lensHub.connect(userTwo).mirror({
|
||||
profileId: secondProfileId,
|
||||
profileIdPointed: FIRST_PROFILE_ID,
|
||||
pubIdPointed: 1,
|
||||
referenceModule: ZERO_ADDRESS,
|
||||
referenceModuleData: [],
|
||||
})
|
||||
).to.not.be.reverted;
|
||||
|
||||
await expect(moduleGlobals.connect(governance).setTreasuryFee(0)).to.not.be.reverted;
|
||||
const data = abiCoder.encode(
|
||||
['address', 'uint256'],
|
||||
[currency.address, DEFAULT_COLLECT_PRICE]
|
||||
);
|
||||
await expect(lensHub.connect(userTwo).follow([FIRST_PROFILE_ID], [[]])).to.not.be.reverted;
|
||||
|
||||
await expect(currency.mint(userTwoAddress, MAX_UINT256)).to.not.be.reverted;
|
||||
await expect(
|
||||
currency.connect(userTwo).approve(feeCollectModule.address, MAX_UINT256)
|
||||
).to.not.be.reverted;
|
||||
|
||||
const tx = lensHub.connect(userTwo).collect(secondProfileId, 1, data);
|
||||
const receipt = await waitForTx(tx);
|
||||
|
||||
let currencyEventCount = 0;
|
||||
for (let log of receipt.logs) {
|
||||
if (log.address == currency.address) {
|
||||
currencyEventCount++;
|
||||
}
|
||||
}
|
||||
expect(currencyEventCount).to.eq(2);
|
||||
|
||||
const expectedReferralAmount = BigNumber.from(DEFAULT_COLLECT_PRICE)
|
||||
.mul(REFERRAL_FEE_BPS)
|
||||
.div(BPS_MAX);
|
||||
const amount = DEFAULT_COLLECT_PRICE.sub(expectedReferralAmount);
|
||||
|
||||
matchEvent(
|
||||
receipt,
|
||||
'Transfer',
|
||||
[userTwoAddress, userAddress, amount],
|
||||
currency,
|
||||
currency.address
|
||||
);
|
||||
|
||||
matchEvent(
|
||||
receipt,
|
||||
'Transfer',
|
||||
[userTwoAddress, userTwoAddress, expectedReferralAmount],
|
||||
currency,
|
||||
currency.address
|
||||
);
|
||||
});
|
||||
|
||||
it('UserTwo should fail to collect without following', async function () {
|
||||
const data = abiCoder.encode(
|
||||
['address', 'uint256'],
|
||||
|
||||
@@ -134,7 +134,7 @@ makeSuiteCleanRoom('Limited Fee Collect Module', function () {
|
||||
).to.be.revertedWith(ERRORS.INIT_PARAMS_INVALID);
|
||||
});
|
||||
|
||||
it('user should fail to post with limited fee collect module using amount lower than max BPS', async function () {
|
||||
it('user should fail to post with limited fee collect module using zero amount', async function () {
|
||||
const collectModuleData = abiCoder.encode(
|
||||
['uint256', 'uint256', 'address', 'address', 'uint16', 'bool'],
|
||||
[DEFAULT_COLLECT_LIMIT, 9999, currency.address, userAddress, REFERRAL_FEE_BPS, true]
|
||||
@@ -177,6 +177,105 @@ makeSuiteCleanRoom('Limited Fee Collect Module', function () {
|
||||
).to.not.be.reverted;
|
||||
});
|
||||
|
||||
it('Governance should set the treasury fee BPS to zero, userTwo collecting should not emit a transfer event to the treasury', async function () {
|
||||
await expect(moduleGlobals.connect(governance).setTreasuryFee(0)).to.not.be.reverted;
|
||||
const data = abiCoder.encode(
|
||||
['address', 'uint256'],
|
||||
[currency.address, DEFAULT_COLLECT_PRICE]
|
||||
);
|
||||
await expect(lensHub.connect(userTwo).follow([FIRST_PROFILE_ID], [[]])).to.not.be.reverted;
|
||||
|
||||
await expect(currency.mint(userTwoAddress, MAX_UINT256)).to.not.be.reverted;
|
||||
await expect(
|
||||
currency.connect(userTwo).approve(limitedFeeCollectModule.address, MAX_UINT256)
|
||||
).to.not.be.reverted;
|
||||
|
||||
const tx = lensHub.connect(userTwo).collect(FIRST_PROFILE_ID, 1, data);
|
||||
const receipt = await waitForTx(tx);
|
||||
|
||||
let currencyEventCount = 0;
|
||||
for (let log of receipt.logs) {
|
||||
if (log.address == currency.address) {
|
||||
currencyEventCount++;
|
||||
}
|
||||
}
|
||||
expect(currencyEventCount).to.eq(1);
|
||||
matchEvent(
|
||||
receipt,
|
||||
'Transfer',
|
||||
[userTwoAddress, userAddress, DEFAULT_COLLECT_PRICE],
|
||||
currency,
|
||||
currency.address
|
||||
);
|
||||
});
|
||||
|
||||
it('UserTwo should mirror the original post, governance should set the treasury fee BPS to zero, userTwo collecting their mirror should not emit a transfer event to the treasury', async function () {
|
||||
const secondProfileId = FIRST_PROFILE_ID + 1;
|
||||
await expect(
|
||||
lensHub.connect(userTwo).createProfile({
|
||||
to: userTwoAddress,
|
||||
handle: 'usertwo',
|
||||
imageURI: MOCK_PROFILE_URI,
|
||||
followModule: ZERO_ADDRESS,
|
||||
followModuleData: [],
|
||||
followNFTURI: MOCK_FOLLOW_NFT_URI,
|
||||
})
|
||||
).to.not.be.reverted;
|
||||
await expect(
|
||||
lensHub.connect(userTwo).mirror({
|
||||
profileId: secondProfileId,
|
||||
profileIdPointed: FIRST_PROFILE_ID,
|
||||
pubIdPointed: 1,
|
||||
referenceModule: ZERO_ADDRESS,
|
||||
referenceModuleData: [],
|
||||
})
|
||||
).to.not.be.reverted;
|
||||
|
||||
await expect(moduleGlobals.connect(governance).setTreasuryFee(0)).to.not.be.reverted;
|
||||
const data = abiCoder.encode(
|
||||
['address', 'uint256'],
|
||||
[currency.address, DEFAULT_COLLECT_PRICE]
|
||||
);
|
||||
await expect(lensHub.connect(userTwo).follow([FIRST_PROFILE_ID], [[]])).to.not.be.reverted;
|
||||
|
||||
await expect(currency.mint(userTwoAddress, MAX_UINT256)).to.not.be.reverted;
|
||||
await expect(
|
||||
currency.connect(userTwo).approve(limitedFeeCollectModule.address, MAX_UINT256)
|
||||
).to.not.be.reverted;
|
||||
|
||||
const tx = lensHub.connect(userTwo).collect(secondProfileId, 1, data);
|
||||
const receipt = await waitForTx(tx);
|
||||
|
||||
let currencyEventCount = 0;
|
||||
for (let log of receipt.logs) {
|
||||
if (log.address == currency.address) {
|
||||
currencyEventCount++;
|
||||
}
|
||||
}
|
||||
expect(currencyEventCount).to.eq(2);
|
||||
|
||||
const expectedReferralAmount = BigNumber.from(DEFAULT_COLLECT_PRICE)
|
||||
.mul(REFERRAL_FEE_BPS)
|
||||
.div(BPS_MAX);
|
||||
const amount = DEFAULT_COLLECT_PRICE.sub(expectedReferralAmount);
|
||||
|
||||
matchEvent(
|
||||
receipt,
|
||||
'Transfer',
|
||||
[userTwoAddress, userAddress, amount],
|
||||
currency,
|
||||
currency.address
|
||||
);
|
||||
|
||||
matchEvent(
|
||||
receipt,
|
||||
'Transfer',
|
||||
[userTwoAddress, userTwoAddress, expectedReferralAmount],
|
||||
currency,
|
||||
currency.address
|
||||
);
|
||||
});
|
||||
|
||||
it('UserTwo should fail to collect without following', async function () {
|
||||
const data = abiCoder.encode(
|
||||
['address', 'uint256'],
|
||||
|
||||
@@ -134,7 +134,7 @@ makeSuiteCleanRoom('Limited Timed Fee Collect Module', function () {
|
||||
).to.be.revertedWith(ERRORS.INIT_PARAMS_INVALID);
|
||||
});
|
||||
|
||||
it('user should fail to post with limited timed fee collect module using amount lower than max BPS', async function () {
|
||||
it('user should fail to post with limited timed fee collect module using zero amount', async function () {
|
||||
const collectModuleData = abiCoder.encode(
|
||||
['uint256', 'uint256', 'address', 'address', 'uint16', 'bool'],
|
||||
[DEFAULT_COLLECT_LIMIT, 9999, currency.address, userAddress, REFERRAL_FEE_BPS, true]
|
||||
@@ -177,6 +177,105 @@ makeSuiteCleanRoom('Limited Timed Fee Collect Module', function () {
|
||||
).to.not.be.reverted;
|
||||
});
|
||||
|
||||
it('Governance should set the treasury fee BPS to zero, userTwo collecting should not emit a transfer event to the treasury', async function () {
|
||||
await expect(moduleGlobals.connect(governance).setTreasuryFee(0)).to.not.be.reverted;
|
||||
const data = abiCoder.encode(
|
||||
['address', 'uint256'],
|
||||
[currency.address, DEFAULT_COLLECT_PRICE]
|
||||
);
|
||||
await expect(lensHub.connect(userTwo).follow([FIRST_PROFILE_ID], [[]])).to.not.be.reverted;
|
||||
|
||||
await expect(currency.mint(userTwoAddress, MAX_UINT256)).to.not.be.reverted;
|
||||
await expect(
|
||||
currency.connect(userTwo).approve(limitedTimedFeeCollectModule.address, MAX_UINT256)
|
||||
).to.not.be.reverted;
|
||||
|
||||
const tx = lensHub.connect(userTwo).collect(FIRST_PROFILE_ID, 1, data);
|
||||
const receipt = await waitForTx(tx);
|
||||
|
||||
let currencyEventCount = 0;
|
||||
for (let log of receipt.logs) {
|
||||
if (log.address == currency.address) {
|
||||
currencyEventCount++;
|
||||
}
|
||||
}
|
||||
expect(currencyEventCount).to.eq(1);
|
||||
matchEvent(
|
||||
receipt,
|
||||
'Transfer',
|
||||
[userTwoAddress, userAddress, DEFAULT_COLLECT_PRICE],
|
||||
currency,
|
||||
currency.address
|
||||
);
|
||||
});
|
||||
|
||||
it('UserTwo should mirror the original post, governance should set the treasury fee BPS to zero, userTwo collecting their mirror should not emit a transfer event to the treasury', async function () {
|
||||
const secondProfileId = FIRST_PROFILE_ID + 1;
|
||||
await expect(
|
||||
lensHub.connect(userTwo).createProfile({
|
||||
to: userTwoAddress,
|
||||
handle: 'usertwo',
|
||||
imageURI: MOCK_PROFILE_URI,
|
||||
followModule: ZERO_ADDRESS,
|
||||
followModuleData: [],
|
||||
followNFTURI: MOCK_FOLLOW_NFT_URI,
|
||||
})
|
||||
).to.not.be.reverted;
|
||||
await expect(
|
||||
lensHub.connect(userTwo).mirror({
|
||||
profileId: secondProfileId,
|
||||
profileIdPointed: FIRST_PROFILE_ID,
|
||||
pubIdPointed: 1,
|
||||
referenceModule: ZERO_ADDRESS,
|
||||
referenceModuleData: [],
|
||||
})
|
||||
).to.not.be.reverted;
|
||||
|
||||
await expect(moduleGlobals.connect(governance).setTreasuryFee(0)).to.not.be.reverted;
|
||||
const data = abiCoder.encode(
|
||||
['address', 'uint256'],
|
||||
[currency.address, DEFAULT_COLLECT_PRICE]
|
||||
);
|
||||
await expect(lensHub.connect(userTwo).follow([FIRST_PROFILE_ID], [[]])).to.not.be.reverted;
|
||||
|
||||
await expect(currency.mint(userTwoAddress, MAX_UINT256)).to.not.be.reverted;
|
||||
await expect(
|
||||
currency.connect(userTwo).approve(limitedTimedFeeCollectModule.address, MAX_UINT256)
|
||||
).to.not.be.reverted;
|
||||
|
||||
const tx = lensHub.connect(userTwo).collect(secondProfileId, 1, data);
|
||||
const receipt = await waitForTx(tx);
|
||||
|
||||
let currencyEventCount = 0;
|
||||
for (let log of receipt.logs) {
|
||||
if (log.address == currency.address) {
|
||||
currencyEventCount++;
|
||||
}
|
||||
}
|
||||
expect(currencyEventCount).to.eq(2);
|
||||
|
||||
const expectedReferralAmount = BigNumber.from(DEFAULT_COLLECT_PRICE)
|
||||
.mul(REFERRAL_FEE_BPS)
|
||||
.div(BPS_MAX);
|
||||
const amount = DEFAULT_COLLECT_PRICE.sub(expectedReferralAmount);
|
||||
|
||||
matchEvent(
|
||||
receipt,
|
||||
'Transfer',
|
||||
[userTwoAddress, userAddress, amount],
|
||||
currency,
|
||||
currency.address
|
||||
);
|
||||
|
||||
matchEvent(
|
||||
receipt,
|
||||
'Transfer',
|
||||
[userTwoAddress, userTwoAddress, expectedReferralAmount],
|
||||
currency,
|
||||
currency.address
|
||||
);
|
||||
});
|
||||
|
||||
it('UserTwo should fail to collect without following', async function () {
|
||||
const data = abiCoder.encode(
|
||||
['address', 'uint256'],
|
||||
|
||||
@@ -102,7 +102,7 @@ makeSuiteCleanRoom('Timed Fee Collect Module', function () {
|
||||
).to.be.revertedWith(ERRORS.INIT_PARAMS_INVALID);
|
||||
});
|
||||
|
||||
it('user should fail to post with timed fee collect module using amount lower than max BPS', async function () {
|
||||
it('user should fail to post with timed fee collect module using zero amount', async function () {
|
||||
const collectModuleData = abiCoder.encode(
|
||||
['uint256', 'address', 'address', 'uint16', 'bool'],
|
||||
[9999, currency.address, userAddress, REFERRAL_FEE_BPS, true]
|
||||
@@ -138,6 +138,105 @@ makeSuiteCleanRoom('Timed Fee Collect Module', function () {
|
||||
).to.not.be.reverted;
|
||||
});
|
||||
|
||||
it('Governance should set the treasury fee BPS to zero, userTwo collecting should not emit a transfer event to the treasury', async function () {
|
||||
await expect(moduleGlobals.connect(governance).setTreasuryFee(0)).to.not.be.reverted;
|
||||
const data = abiCoder.encode(
|
||||
['address', 'uint256'],
|
||||
[currency.address, DEFAULT_COLLECT_PRICE]
|
||||
);
|
||||
await expect(lensHub.connect(userTwo).follow([FIRST_PROFILE_ID], [[]])).to.not.be.reverted;
|
||||
|
||||
await expect(currency.mint(userTwoAddress, MAX_UINT256)).to.not.be.reverted;
|
||||
await expect(
|
||||
currency.connect(userTwo).approve(timedFeeCollectModule.address, MAX_UINT256)
|
||||
).to.not.be.reverted;
|
||||
|
||||
const tx = lensHub.connect(userTwo).collect(FIRST_PROFILE_ID, 1, data);
|
||||
const receipt = await waitForTx(tx);
|
||||
|
||||
let currencyEventCount = 0;
|
||||
for (let log of receipt.logs) {
|
||||
if (log.address == currency.address) {
|
||||
currencyEventCount++;
|
||||
}
|
||||
}
|
||||
expect(currencyEventCount).to.eq(1);
|
||||
matchEvent(
|
||||
receipt,
|
||||
'Transfer',
|
||||
[userTwoAddress, userAddress, DEFAULT_COLLECT_PRICE],
|
||||
currency,
|
||||
currency.address
|
||||
);
|
||||
});
|
||||
|
||||
it('UserTwo should mirror the original post, governance should set the treasury fee BPS to zero, userTwo collecting their mirror should not emit a transfer event to the treasury', async function () {
|
||||
const secondProfileId = FIRST_PROFILE_ID + 1;
|
||||
await expect(
|
||||
lensHub.connect(userTwo).createProfile({
|
||||
to: userTwoAddress,
|
||||
handle: 'usertwo',
|
||||
imageURI: MOCK_PROFILE_URI,
|
||||
followModule: ZERO_ADDRESS,
|
||||
followModuleData: [],
|
||||
followNFTURI: MOCK_FOLLOW_NFT_URI,
|
||||
})
|
||||
).to.not.be.reverted;
|
||||
await expect(
|
||||
lensHub.connect(userTwo).mirror({
|
||||
profileId: secondProfileId,
|
||||
profileIdPointed: FIRST_PROFILE_ID,
|
||||
pubIdPointed: 1,
|
||||
referenceModule: ZERO_ADDRESS,
|
||||
referenceModuleData: [],
|
||||
})
|
||||
).to.not.be.reverted;
|
||||
|
||||
await expect(moduleGlobals.connect(governance).setTreasuryFee(0)).to.not.be.reverted;
|
||||
const data = abiCoder.encode(
|
||||
['address', 'uint256'],
|
||||
[currency.address, DEFAULT_COLLECT_PRICE]
|
||||
);
|
||||
await expect(lensHub.connect(userTwo).follow([FIRST_PROFILE_ID], [[]])).to.not.be.reverted;
|
||||
|
||||
await expect(currency.mint(userTwoAddress, MAX_UINT256)).to.not.be.reverted;
|
||||
await expect(
|
||||
currency.connect(userTwo).approve(timedFeeCollectModule.address, MAX_UINT256)
|
||||
).to.not.be.reverted;
|
||||
|
||||
const tx = lensHub.connect(userTwo).collect(secondProfileId, 1, data);
|
||||
const receipt = await waitForTx(tx);
|
||||
|
||||
let currencyEventCount = 0;
|
||||
for (let log of receipt.logs) {
|
||||
if (log.address == currency.address) {
|
||||
currencyEventCount++;
|
||||
}
|
||||
}
|
||||
expect(currencyEventCount).to.eq(2);
|
||||
|
||||
const expectedReferralAmount = BigNumber.from(DEFAULT_COLLECT_PRICE)
|
||||
.mul(REFERRAL_FEE_BPS)
|
||||
.div(BPS_MAX);
|
||||
const amount = DEFAULT_COLLECT_PRICE.sub(expectedReferralAmount);
|
||||
|
||||
matchEvent(
|
||||
receipt,
|
||||
'Transfer',
|
||||
[userTwoAddress, userAddress, amount],
|
||||
currency,
|
||||
currency.address
|
||||
);
|
||||
|
||||
matchEvent(
|
||||
receipt,
|
||||
'Transfer',
|
||||
[userTwoAddress, userTwoAddress, expectedReferralAmount],
|
||||
currency,
|
||||
currency.address
|
||||
);
|
||||
});
|
||||
|
||||
it('UserTwo should fail to collect without following', async function () {
|
||||
const data = abiCoder.encode(
|
||||
['address', 'uint256'],
|
||||
|
||||
@@ -76,10 +76,10 @@ makeSuiteCleanRoom('Fee Follow Module', function () {
|
||||
).to.be.revertedWith(ERRORS.INIT_PARAMS_INVALID);
|
||||
});
|
||||
|
||||
it('user should fail to create a profile with fee follow module using amount lower than max BPS', async function () {
|
||||
it('user should fail to create a profile with fee follow module using zero amount', async function () {
|
||||
const followModuleData = abiCoder.encode(
|
||||
['uint256', 'address', 'address'],
|
||||
[9999, currency.address, userAddress]
|
||||
[0, currency.address, userAddress]
|
||||
);
|
||||
|
||||
await expect(
|
||||
@@ -113,6 +113,36 @@ makeSuiteCleanRoom('Fee Follow Module', function () {
|
||||
).to.not.be.reverted;
|
||||
});
|
||||
|
||||
it('Governance should set the treasury fee BPS to zero, userTwo following should not emit a transfer event to the treasury', async function () {
|
||||
await expect(moduleGlobals.connect(governance).setTreasuryFee(0)).to.not.be.reverted;
|
||||
const data = abiCoder.encode(
|
||||
['address', 'uint256'],
|
||||
[currency.address, DEFAULT_FOLLOW_PRICE]
|
||||
);
|
||||
await expect(currency.mint(userTwoAddress, MAX_UINT256)).to.not.be.reverted;
|
||||
await expect(
|
||||
currency.connect(userTwo).approve(feeFollowModule.address, MAX_UINT256)
|
||||
).to.not.be.reverted;
|
||||
|
||||
const tx = lensHub.connect(userTwo).follow([FIRST_PROFILE_ID], [data]);
|
||||
const receipt = await waitForTx(tx);
|
||||
|
||||
let currencyEventCount = 0;
|
||||
for (let log of receipt.logs) {
|
||||
if (log.address == currency.address) {
|
||||
currencyEventCount++;
|
||||
}
|
||||
}
|
||||
expect(currencyEventCount).to.eq(1);
|
||||
matchEvent(
|
||||
receipt,
|
||||
'Transfer',
|
||||
[userTwoAddress, userAddress, DEFAULT_FOLLOW_PRICE],
|
||||
currency,
|
||||
currency.address
|
||||
);
|
||||
});
|
||||
|
||||
it('UserTwo should fail to follow passing a different expected price in data', async function () {
|
||||
const data = abiCoder.encode(
|
||||
['address', 'uint256'],
|
||||
|
||||
@@ -4,7 +4,11 @@ import { keccak256, toUtf8Bytes } from 'ethers/lib/utils';
|
||||
import { UIDataProvider__factory } from '../../typechain-types';
|
||||
import { ZERO_ADDRESS } from '../helpers/constants';
|
||||
import { ERRORS } from '../helpers/errors';
|
||||
import { getJsonMetadataFromBase64TokenUri } from '../helpers/utils';
|
||||
import {
|
||||
getDecodedSvgImage,
|
||||
getMetadataFromBase64TokenUri,
|
||||
loadTestResourceAsUtf8String,
|
||||
} from '../helpers/utils';
|
||||
import {
|
||||
approvalFollowModule,
|
||||
deployer,
|
||||
@@ -156,20 +160,20 @@ makeSuiteCleanRoom('Misc', function () {
|
||||
});
|
||||
|
||||
it('Profile tokenURI should return the accurate URI', async function () {
|
||||
const tokenURI = await lensHub.tokenURI(FIRST_PROFILE_ID);
|
||||
const jsonMetadata = await getJsonMetadataFromBase64TokenUri(tokenURI);
|
||||
expect(jsonMetadata.name).to.eq(`@${MOCK_PROFILE_HANDLE}`);
|
||||
expect(jsonMetadata.description).to.eq(`@${MOCK_PROFILE_HANDLE} - Lens profile`);
|
||||
const tokenUri = await lensHub.tokenURI(FIRST_PROFILE_ID);
|
||||
const metadata = await getMetadataFromBase64TokenUri(tokenUri);
|
||||
expect(metadata.name).to.eq(`@${MOCK_PROFILE_HANDLE}`);
|
||||
expect(metadata.description).to.eq(`@${MOCK_PROFILE_HANDLE} - Lens profile`);
|
||||
const expectedAttributes = [
|
||||
{ trait_type: 'id', value: `#${FIRST_PROFILE_ID.toString()}` },
|
||||
{ trait_type: 'followers', value: '0' },
|
||||
{ trait_type: 'owner', value: userAddress.toLowerCase() },
|
||||
{ trait_type: 'handle', value: `@${MOCK_PROFILE_HANDLE}` },
|
||||
];
|
||||
expect(jsonMetadata.attributes).to.eql(expectedAttributes);
|
||||
expect(keccak256(toUtf8Bytes(tokenURI))).to.eq(
|
||||
'0xa2e9967e825705ce8f38931f7d1f88fe63bef6f2f2c52715692f14d42d889b76'
|
||||
);
|
||||
expect(metadata.attributes).to.eql(expectedAttributes);
|
||||
const actualSvg = await getDecodedSvgImage(metadata);
|
||||
const expectedSvg = loadTestResourceAsUtf8String('profile-token-uri-images/mock-profile.svg');
|
||||
expect(actualSvg).to.eq(expectedSvg);
|
||||
});
|
||||
|
||||
it('Publication reference module getter should return the correct reference module (or zero in case of no reference module)', async function () {
|
||||
|
||||
1
test/resources/profile-token-uri-images/default.svg
Normal file
1
test/resources/profile-token-uri-images/default.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 12 KiB |
1
test/resources/profile-token-uri-images/mock-profile.svg
Normal file
1
test/resources/profile-token-uri-images/mock-profile.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 9.6 KiB |
1
test/resources/profile-token-uri-images/mock.svg
Normal file
1
test/resources/profile-token-uri-images/mock.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 9.6 KiB |
1
test/resources/profile-token-uri-images/other-mock.svg
Normal file
1
test/resources/profile-token-uri-images/other-mock.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 9.6 KiB |
Reference in New Issue
Block a user