diff --git a/contracts/Mocks/FallbackExtensions/Buy.sol b/contracts/Mocks/FallbackExtensions/Buy.sol new file mode 100644 index 000000000..447af2af4 --- /dev/null +++ b/contracts/Mocks/FallbackExtensions/Buy.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.4; + +/** + * @dev This contract is used only for testing purposes + */ +contract BuyExtension { + function buy() public payable returns (uint256) { + return address(this).balance; + } +} diff --git a/contracts/Mocks/FallbackExtensions/CheckerExtension.sol b/contracts/Mocks/FallbackExtensions/CheckerExtension.sol index a10179460..e49ade20a 100644 --- a/contracts/Mocks/FallbackExtensions/CheckerExtension.sol +++ b/contracts/Mocks/FallbackExtensions/CheckerExtension.sol @@ -13,7 +13,7 @@ contract CheckerExtension { function checkMsgVariable( address originalMsgSender, uint256 originalMsgValue - ) public pure returns (bool) { + ) public payable returns (bool) { if (msg.data.length != 120) revert(); if ( originalMsgSender != diff --git a/tests/LSP17ContractExtension/LSP17Extendable.behaviour.ts b/tests/LSP17ContractExtension/LSP17Extendable.behaviour.ts index 411ded9ec..6c84c6dd8 100644 --- a/tests/LSP17ContractExtension/LSP17Extendable.behaviour.ts +++ b/tests/LSP17ContractExtension/LSP17Extendable.behaviour.ts @@ -34,8 +34,8 @@ import { ERC725YDataKeys } from '../../constants'; export type LSP17TestContext = { accounts: SignerWithAddress[]; - contract: LSP0ERC725Account | LSP9Vault; - deployParams: { owner: SignerWithAddress }; + contract: LSP0ERC725Account | LSP9Vault | any; + deployParams: any; }; export const shouldBehaveLikeLSP17 = (buildContext: () => Promise) => { diff --git a/tests/LSP17ContractExtension/LSP17ExtendableTokens.behaviour.ts b/tests/LSP17ContractExtension/LSP17ExtendableTokens.behaviour.ts new file mode 100644 index 000000000..da6efd3bc --- /dev/null +++ b/tests/LSP17ContractExtension/LSP17ExtendableTokens.behaviour.ts @@ -0,0 +1,669 @@ +import { expect } from 'chai'; +import { ethers } from 'hardhat'; +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; +import { FakeContract, smock } from '@defi-wonderland/smock'; + +import { + LSP0ERC725Account, + LSP9Vault, + CheckerExtension__factory, + ERC165Extension, + ERC165Extension__factory, + RevertStringExtension__factory, + RevertCustomExtension, + RevertCustomExtension__factory, + EmitEventExtension, + EmitEventExtension__factory, + TransferExtension__factory, + TransferExtension, + ReenterAccountExtension__factory, + ReenterAccountExtension, + OnERC721ReceivedExtension, + OnERC721ReceivedExtension__factory, + RequireCallbackToken, + RequireCallbackToken__factory, + RevertFallbackExtension, + RevertFallbackExtension__factory, + BuyExtension, + BuyExtension__factory, +} from '../../types'; + +// helpers +import { abiCoder, provider } from '../utils/helpers'; + +// constants +import { ERC725YDataKeys } from '../../constants'; + +export type LSP17TestContext = { + accounts: SignerWithAddress[]; + contract: LSP0ERC725Account | LSP9Vault | any; + deployParams: any; +}; + +export const shouldBehaveLikeLSP17 = (buildContext: () => Promise) => { + let context: LSP17TestContext; + let notExistingFunctionSignature, + onERC721ReceivedFunctionSelector, + checkMsgVariableFunctionSelector, + nameFunctionSelector, + ageFunctionSelector, + transferFunctionSelector, + reenterAccountFunctionSelector, + revertStringFunctionSelector, + revertCustomFunctionSelector, + emitEventFunctionSelector, + buyFunctionSelector, + supportsInterfaceFunctionSelector; + + let checkMsgVariableFunctionExtensionHandlerKey, + nameFunctionExtensionHandlerKey, + ageFunctionExtensionHandlerKey, + transferFunctionExtensionHandlerKey, + reenterAccountFunctionExtensionHandlerKey, + revertStringFunctionExtensionHandlerKey, + revertCustomFunctionExtensionHandlerKey, + emitEventFunctionExtensionHandlerKey, + onERC721ReceivedFunctionExtensionHandlerKey, + buyFunctionExtensionHandlerKey, + supportsInterfaceFunctionExtensionHandlerKey; + + + before(async () => { + context = await buildContext(); + + // withdraw() + notExistingFunctionSignature = '0x3ccfd60b'; + + // checkMsgVariable(address,uint256) + checkMsgVariableFunctionSelector = '0xe825d37d'; + + // name() + nameFunctionSelector = '0x06fdde03'; + + // age() + ageFunctionSelector = '0x262a9dff'; + + // transfer(uint256) + transferFunctionSelector = '0x12514bba'; + + // revertString(string) + revertStringFunctionSelector = '0xb678618b'; + + // revertCustom() + revertCustomFunctionSelector = '0x1ed106b8'; + + // emitEvent() + emitEventFunctionSelector = '0x7b0cb839'; + + // reenterAccount(bytes) + reenterAccountFunctionSelector = '0x864e5589'; + + // onERC721Received(address,address,uint256,bytes) + onERC721ReceivedFunctionSelector = '0x150b7a02'; + + // buy() + buyFunctionSelector = '0xa6f2ae3a'; + + // supportsInterface(bytes4) + supportsInterfaceFunctionSelector = '0x01ffc9a7'; + + checkMsgVariableFunctionExtensionHandlerKey = + ERC725YDataKeys.LSP17.LSP17ExtensionPrefix + + checkMsgVariableFunctionSelector.substring(2) + + '00000000000000000000000000000000'; // zero padded + + nameFunctionExtensionHandlerKey = + ERC725YDataKeys.LSP17.LSP17ExtensionPrefix + + nameFunctionSelector.substring(2) + + '00000000000000000000000000000000'; // zero padded + + ageFunctionExtensionHandlerKey = + ERC725YDataKeys.LSP17.LSP17ExtensionPrefix + + ageFunctionSelector.substring(2) + + '00000000000000000000000000000000'; // zero padded + + transferFunctionExtensionHandlerKey = + ERC725YDataKeys.LSP17.LSP17ExtensionPrefix + + transferFunctionSelector.substring(2) + + '00000000000000000000000000000000'; // zero padded + + reenterAccountFunctionExtensionHandlerKey = + ERC725YDataKeys.LSP17.LSP17ExtensionPrefix + + reenterAccountFunctionSelector.substring(2) + + '00000000000000000000000000000000'; // zero padded + + revertStringFunctionExtensionHandlerKey = + ERC725YDataKeys.LSP17.LSP17ExtensionPrefix + + revertStringFunctionSelector.substring(2) + + '00000000000000000000000000000000'; // zero padded + + revertCustomFunctionExtensionHandlerKey = + ERC725YDataKeys.LSP17.LSP17ExtensionPrefix + + revertCustomFunctionSelector.substring(2) + + '00000000000000000000000000000000'; // zero padded + + emitEventFunctionExtensionHandlerKey = + ERC725YDataKeys.LSP17.LSP17ExtensionPrefix + + emitEventFunctionSelector.substring(2) + + '00000000000000000000000000000000'; // zero padded + + onERC721ReceivedFunctionExtensionHandlerKey = + ERC725YDataKeys.LSP17.LSP17ExtensionPrefix + + onERC721ReceivedFunctionSelector.substring(2) + + '00000000000000000000000000000000'; // zero padded + + buyFunctionExtensionHandlerKey = + ERC725YDataKeys.LSP17.LSP17ExtensionPrefix + + buyFunctionSelector.substring(2) + + '00000000000000000000000000000000'; // zero padded + + supportsInterfaceFunctionExtensionHandlerKey = + ERC725YDataKeys.LSP17.LSP17ExtensionPrefix + + supportsInterfaceFunctionSelector.substring(2) + + '00000000000000000000000000000000'; // zero padded + }); + + describe('when calling the contract with empty calldata', () => { + describe('when making a call without any value', () => { + it('should revert', async () => { + await expect( + context.accounts[0].sendTransaction({ + to: context.contract.address, + }), + ).to.be.revertedWithCustomError(context.contract, 'InvalidFunctionSelector'); + }); + }); + + describe('when making a call with sending value', () => { + it('should revert', async () => { + const amountSent = 200; + await expect( + context.accounts[0].sendTransaction({ + to: context.contract.address, + value: amountSent, + }), + ).to.be.revertedWithCustomError(context.contract, 'InvalidFunctionSelector'); + }); + }); + }); + + describe('when calling the contract with calldata', () => { + describe("when calling method that doesn't exist", () => { + describe('when there is no extension for the function called', () => { + describe('when calling without sending any value', () => { + it('should revert with NoExtensionForFunctionSignature error', async () => { + await expect( + context.accounts[0].sendTransaction({ + to: context.contract.address, + data: notExistingFunctionSignature, + }), + ) + .to.be.revertedWithCustomError( + context.contract, + 'NoExtensionFoundForFunctionSelector', + ) + .withArgs(notExistingFunctionSignature); + }); + }); + + describe('when calling with sending value', () => { + it('should revert with NoExtensionForFunctionSignature error', async () => { + const amountSent = 200; + await expect( + context.accounts[0].sendTransaction({ + to: context.contract.address, + data: notExistingFunctionSignature, + value: amountSent, + }), + ) + .to.be.revertedWithCustomError( + context.contract, + 'NoExtensionFoundForFunctionSelector', + ) + .withArgs(notExistingFunctionSignature); + }); + }); + }); + + describe('when there is an extension for the function called', () => { + describe('when double checking that the msg.sender & msg.value were sent with the calldata to the extension', () => { + describe('when relying on the Checker Extension', () => { + describe('when the extension is not set yet', () => { + it('should revert with NoExtensionFoundForFunctionSelector', async () => { + const supposedSender = context.accounts[0]; + const value = 200; + const checkMsgVariableFunctionSignature = + checkMsgVariableFunctionSelector + + abiCoder + .encode(['address', 'uint256'], [supposedSender.address, value]) + .substring(2); + + // different sender + await expect( + context.accounts[1].sendTransaction({ + to: context.contract.address, + data: checkMsgVariableFunctionSignature, + value: value, + }), + ) + .to.be.revertedWithCustomError( + context.contract, + 'NoExtensionFoundForFunctionSelector', + ) + .withArgs(checkMsgVariableFunctionSelector); + }); + + it('should revert with NoExtensionFoundForFunctionSelector, even if passed a different value from the msg.value', async () => { + const sender = context.accounts[0]; + const supposedValue = 200; + const checkMsgVariableFunctionSignature = + checkMsgVariableFunctionSelector + + abiCoder + .encode(['address', 'uint256'], [sender.address, supposedValue]) + .substring(2); + + await expect( + sender.sendTransaction({ + to: context.contract.address, + data: checkMsgVariableFunctionSignature, + value: 100, // different value + }), + ) + .to.be.revertedWithCustomError( + context.contract, + 'NoExtensionFoundForFunctionSelector', + ) + .withArgs(checkMsgVariableFunctionSelector); + }); + }); + + describe('when the extension is set', () => { + before(async () => { + const checkerExtension = await new CheckerExtension__factory( + context.accounts[0], + ).deploy(); + + await context.contract + .connect(context.deployParams.owner) + .setData(checkMsgVariableFunctionExtensionHandlerKey, checkerExtension.address); + }); + + it('should fail if passed a different value from the msg.value', async () => { + const sender = context.accounts[0]; + const supposedValue = 200; + const checkMsgVariableFunctionSignature = + checkMsgVariableFunctionSelector + + abiCoder + .encode(['address', 'uint256'], [sender.address, supposedValue]) + .substring(2); + + await expect( + sender.sendTransaction({ + to: context.contract.address, + data: checkMsgVariableFunctionSignature, + value: 100, // different value + }), + ).to.be.reverted; + }); + + it('should fail if passed a different address from the msg.sender', async () => { + const supposedSender = context.accounts[0]; + const value = 200; + const checkMsgVariableFunctionSignature = + checkMsgVariableFunctionSelector + + abiCoder + .encode(['address', 'uint256'], [supposedSender.address, value]) + .substring(2); + + // different sender + await expect( + context.accounts[1].sendTransaction({ + to: context.contract.address, + data: checkMsgVariableFunctionSignature, + value: value, + }), + ).to.be.reverted; + }); + + it('should pass if passed the same address and value as the msg.sender and msg.value', async () => { + const sender = context.accounts[0]; + const value = 200; + const checkMsgVariableFunctionSignature = + checkMsgVariableFunctionSelector + + abiCoder.encode(['address', 'uint256'], [sender.address, value]).substring(2); + + await sender.sendTransaction({ + to: context.contract.address, + data: checkMsgVariableFunctionSignature, + value: value, + }); + }); + }); + }); + }); + + describe('when calling an extension that reverts with string error', () => { + before(async () => { + const revertStringExtension = await new RevertStringExtension__factory( + context.accounts[0], + ).deploy(); + + await context.contract + .connect(context.deployParams.owner) + .setData(revertStringFunctionExtensionHandlerKey, revertStringExtension.address); + }); + + it('should revert with a string error provided as argument', async () => { + const revertString = 'I failed'; + + const revertStringFunctionSignature = + revertStringFunctionSelector + + abiCoder.encode(['string'], [revertString]).substring(2); + + await expect( + context.accounts[0].sendTransaction({ + to: context.contract.address, + data: revertStringFunctionSignature, + value: 0, + }), + ).to.be.revertedWith(revertString); + }); + }); + + describe('when calling an extension that reverts with Custom error with tx.origin and msg.sender as parameters', () => { + let revertCustomExtension: RevertCustomExtension; + + before(async () => { + revertCustomExtension = await new RevertCustomExtension__factory( + context.accounts[0], + ).deploy(); + + await context.contract + .connect(context.deployParams.owner) + .setData(revertCustomFunctionExtensionHandlerKey, revertCustomExtension.address); + }); + + it('should revert with a custom error with tx.origin and msg.sender as argument', async () => { + const sender = context.accounts[0]; + + await expect( + sender.sendTransaction({ + to: context.contract.address, + data: revertCustomFunctionSelector, + value: 0, + }), + ) + .to.be.revertedWithCustomError(revertCustomExtension, 'RevertWithAddresses') + .withArgs(sender.address, context.contract.address); + }); + }); + + describe('when calling an extension that emits an event', () => { + let emitEventExtension: EmitEventExtension; + + before(async () => { + emitEventExtension = await new EmitEventExtension__factory( + context.accounts[0], + ).deploy(); + + await context.contract + .connect(context.deployParams.owner) + .setData(emitEventFunctionExtensionHandlerKey, emitEventExtension.address); + }); + + it('should pass and emit the event on the extension', async () => { + await expect( + context.accounts[0].sendTransaction({ + to: context.contract.address, + data: emitEventFunctionSelector, + value: 0, + }), + ).to.emit(emitEventExtension, 'EventEmittedInExtension'); + }); + }); + + describe('when calling an extension that returns a string', () => { + let nameExtension: FakeContract; + + before(async () => { + nameExtension = await smock.fake([ + { + inputs: [], + name: 'name', + outputs: [ + { + internalType: 'string', + name: '', + type: 'string', + }, + ], + stateMutability: 'view', + type: 'function', + }, + ]); + nameExtension.name.returns('LUKSO'); + + await context.contract + .connect(context.deployParams.owner) + .setData(nameFunctionExtensionHandlerKey, nameExtension.address); + }); + + it('should pass and return the name correctly', async () => { + const returnValue = await provider.call({ + from: context.accounts[0].address, + to: context.contract.address, + data: nameFunctionSelector, + }); + + expect(returnValue).to.equal(abiCoder.encode(['string'], ['LUKSO'])); + }); + }); + + describe('when calling an extension that returns a number', () => { + let ageExtension: FakeContract; + + before(async () => { + ageExtension = await smock.fake([ + { + inputs: [], + name: 'age', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + ]); + ageExtension.age.returns(20); + + await context.contract + .connect(context.deployParams.owner) + .setData(ageFunctionExtensionHandlerKey, ageExtension.address); + }); + + it('should pass and return the age correctly', async () => { + const returnValue = await provider.call({ + from: context.accounts[0].address, + to: context.contract.address, + data: ageFunctionSelector, + }); + + expect(returnValue).to.equal(abiCoder.encode(['uint256'], [20])); + }); + }); + + describe('when calling an extension that modify the state of the extension', () => { + let transferExtension: TransferExtension; + + before(async () => { + transferExtension = await new TransferExtension__factory(context.accounts[0]).deploy(); + + await context.contract + .connect(context.deployParams.owner) + .setData(transferFunctionExtensionHandlerKey, transferExtension.address); + }); + + it('should pass and change the state accordingly', async () => { + const balanceBefore = await transferExtension.callStatic.balances( + context.accounts[0].address, + ); + + expect(balanceBefore).to.equal(0); + + const amountTransferred = 20; + + const transferFunctionSignature = + transferFunctionSelector + + abiCoder.encode(['uint256'], [amountTransferred]).substring(2); + + await context.accounts[0].sendTransaction({ + to: context.contract.address, + data: transferFunctionSignature, + }); + + const balanceAfter = await transferExtension.callStatic.balances( + context.accounts[0].address, + ); + + expect(balanceAfter).to.equal(amountTransferred); + }); + }); + + describe('when calling a payable extension with value', () => { + let buyExtension: BuyExtension; + + before(async () => { + buyExtension = await new BuyExtension__factory(context.accounts[0]).deploy(); + + await context.contract + .connect(context.deployParams.owner) + .setData(buyFunctionExtensionHandlerKey, buyExtension.address); + }); + + it('should pass and receive the value sent within the contract', async () => { + const balanceBefore = await provider.getBalance(buyExtension.address); + + expect(balanceBefore).to.equal(0); + + await context.accounts[0].sendTransaction({ + to: context.contract.address, + value: 100, + data: buyFunctionSelector, + }); + + const balanceAfter = await provider.getBalance(buyExtension.address); + + expect(balanceAfter).to.equal(100); + }); + }) + + describe('when calling an extension that reenter the fallback function of the account', () => { + let reenterAccountExtension: ReenterAccountExtension; + + before(async () => { + reenterAccountExtension = await new ReenterAccountExtension__factory( + context.accounts[0], + ).deploy(); + + await context.contract + .connect(context.deployParams.owner) + .setData(reenterAccountFunctionExtensionHandlerKey, reenterAccountExtension.address); + }); + + describe('when reentering with a call to an extension that emits an event', () => { + describe('when reentering before setting the extension', () => { + let emitEventExtension: EmitEventExtension; + + before(async () => { + emitEventExtension = await new EmitEventExtension__factory( + context.accounts[0], + ).deploy(); + }); + + it('should not emit any event', async () => { + const reenterAccountFunctionSignature = + reenterAccountFunctionSelector + + abiCoder.encode(['bytes'], [emitEventFunctionSelector]).substring(2); + + await expect( + context.accounts[0].sendTransaction({ + to: context.contract.address, + data: reenterAccountFunctionSignature, + }), + ).to.not.emit(emitEventExtension, 'EventEmittedInExtension'); + }); + }); + describe('when reentering after setting the extension', () => { + let emitEventExtension: EmitEventExtension; + + before(async () => { + emitEventExtension = await new EmitEventExtension__factory( + context.accounts[0], + ).deploy(); + + await context.contract + .connect(context.deployParams.owner) + .setData(emitEventFunctionExtensionHandlerKey, emitEventExtension.address); + }); + it('should emit the event on 3rd extension called', async () => { + const reenterAccountFunctionSignature = + reenterAccountFunctionSelector + + abiCoder.encode(['bytes'], [emitEventFunctionSelector]).substring(2); + + await expect( + context.accounts[0].sendTransaction({ + to: context.contract.address, + data: reenterAccountFunctionSignature, + value: 0, + }), + ).to.emit(emitEventExtension, 'EventEmittedInExtension'); + }); + }); + }); + }); + + describe('when calling the supportsInterface of the extendable contract with `0xaabbccdd` value', () => { + describe('when the ERC165 extension was not set', () => { + it('should return false', async () => { + expect(await context.contract.supportsInterface('0xaabbccdd')).to.be.false; + }); + }); + + describe('when the ERC165 extension was set', () => { + let erc165Extension: ERC165Extension; + before(async () => { + erc165Extension = await new ERC165Extension__factory(context.accounts[0]).deploy(); + + await context.contract + .connect(context.deployParams.owner) + .setData(supportsInterfaceFunctionExtensionHandlerKey, erc165Extension.address); + }); + + it('should return true', async () => { + expect(await context.contract.supportsInterface('0xaabbccdd')).to.be.true; + }); + }); + }); + }); + }); + + describe('when calling with calldata that is not checked for extension', () => { + describe('when calling with a payload of length less than 4bytes', () => { + let revertFallbackExtension: RevertFallbackExtension; + + it('should revert', async () => { + await expect( + context.accounts[0].sendTransaction({ + to: context.contract.address, + data: '0x01', + }), + ).to.be.revertedWithCustomError(context.contract, 'InvalidFunctionSelector'); + }); + }); + }); + }); +}; diff --git a/tests/LSP7DigitalAsset/LSP7DigitalAsset.behaviour.ts b/tests/LSP7DigitalAsset/LSP7DigitalAsset.behaviour.ts index fe7ecd03d..2393922ee 100644 --- a/tests/LSP7DigitalAsset/LSP7DigitalAsset.behaviour.ts +++ b/tests/LSP7DigitalAsset/LSP7DigitalAsset.behaviour.ts @@ -1999,6 +1999,10 @@ export const shouldInitializeLikeLSP7 = ( expect(await context.lsp7.supportsInterface(INTERFACE_IDS.LSP7DigitalAsset)); }); + it('should have registered the LSP17Extendable interface', async () => { + expect(await context.lsp7.supportsInterface(INTERFACE_IDS.LSP17Extendable)); + }); + it('should have set expected entries with ERC725Y.setData', async () => { await expect(context.initializeTransaction) .to.emit(context.lsp7, 'DataChanged') diff --git a/tests/LSP7DigitalAsset/standard/LSP7DigitalAsset.test.ts b/tests/LSP7DigitalAsset/standard/LSP7DigitalAsset.test.ts index 9d41770f5..94869a442 100644 --- a/tests/LSP7DigitalAsset/standard/LSP7DigitalAsset.test.ts +++ b/tests/LSP7DigitalAsset/standard/LSP7DigitalAsset.test.ts @@ -10,6 +10,11 @@ import { LSP7TestContext, } from '../LSP7DigitalAsset.behaviour'; +import { + LSP17TestContext, + shouldBehaveLikeLSP17, +} from '../../LSP17ContractExtension/LSP17ExtendableTokens.behaviour'; + import { LS4DigitalAssetMetadataTestContext, shouldBehaveLikeLSP4DigitalAssetMetadata, @@ -53,6 +58,24 @@ describe('LSP7DigitalAsset with constructor', () => { }; }; + const buildLSP17TestContext = async (): Promise => { + const accounts = await ethers.getSigners(); + + const deployParams = { + name: 'LSP8 - deployed with constructor', + symbol: 'NFT', + owner: accounts[0], + }; + + const contract = await new LSP7Tester__factory(accounts[0]).deploy( + deployParams.name, + deployParams.symbol, + deployParams.owner.address, + ); + + return { accounts, contract, deployParams }; + }; + describe('when deploying the contract', () => { it('should revert when deploying with address(0) as owner', async () => { const accounts = await ethers.getSigners(); @@ -93,5 +116,6 @@ describe('LSP7DigitalAsset with constructor', () => { describe('when testing deployed contract', () => { shouldBehaveLikeLSP4DigitalAssetMetadata(buildLSP4DigitalAssetMetadataTestContext); shouldBehaveLikeLSP7(buildTestContext); + shouldBehaveLikeLSP17(buildLSP17TestContext); }); }); diff --git a/tests/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAsset.behaviour.ts b/tests/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAsset.behaviour.ts index 23701e075..9efd37cf2 100644 --- a/tests/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAsset.behaviour.ts +++ b/tests/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAsset.behaviour.ts @@ -1641,6 +1641,10 @@ export const shouldInitializeLikeLSP8 = ( expect(await context.lsp8.supportsInterface(INTERFACE_IDS.LSP8IdentifiableDigitalAsset)); }); + it('should have registered the LSP17Extendable interface', async () => { + expect(await context.lsp8.supportsInterface(INTERFACE_IDS.LSP17Extendable)); + }); + it('should have set expected entries with ERC725Y.setData', async () => { await expect(context.initializeTransaction) .to.emit(context.lsp8, 'DataChanged') diff --git a/tests/LSP8IdentifiableDigitalAsset/standard/LSP8IdentifiableDigitalAsset.test.ts b/tests/LSP8IdentifiableDigitalAsset/standard/LSP8IdentifiableDigitalAsset.test.ts index 4787028a2..e1263a0e2 100644 --- a/tests/LSP8IdentifiableDigitalAsset/standard/LSP8IdentifiableDigitalAsset.test.ts +++ b/tests/LSP8IdentifiableDigitalAsset/standard/LSP8IdentifiableDigitalAsset.test.ts @@ -10,6 +10,11 @@ import { LSP8TestContext, } from '../LSP8IdentifiableDigitalAsset.behaviour'; +import { + LSP17TestContext, + shouldBehaveLikeLSP17, +} from '../../LSP17ContractExtension/LSP17ExtendableTokens.behaviour'; + import { LS4DigitalAssetMetadataTestContext, shouldBehaveLikeLSP4DigitalAssetMetadata, @@ -48,6 +53,23 @@ describe('LSP8IdentifiableDigitalAsset with constructor', () => { }; }; + const buildLSP17TestContext = async (): Promise => { + const accounts = await ethers.getSigners(); + + const deployParams = { + name: 'LSP8 - deployed with constructor', + symbol: 'NFT', + owner: accounts[0], + }; + const contract = await new LSP8Tester__factory(accounts[0]).deploy( + deployParams.name, + deployParams.symbol, + deployParams.owner.address, + ); + + return { accounts, contract, deployParams }; + }; + describe('when deploying the contract', () => { it('should revert when deploying with address(0) as owner', async () => { const accounts = await ethers.getSigners(); @@ -89,5 +111,6 @@ describe('LSP8IdentifiableDigitalAsset with constructor', () => { describe('when testing deployed contract', () => { shouldBehaveLikeLSP4DigitalAssetMetadata(buildLSP4DigitalAssetMetadataTestContext); shouldBehaveLikeLSP8(buildTestContext); + shouldBehaveLikeLSP17(buildLSP17TestContext); }); });