Skip to content

Commit

Permalink
More Tests & Interface Additions (#111)
Browse files Browse the repository at this point in the history
  • Loading branch information
jdubpark authored Apr 16, 2024
1 parent 9471890 commit 0a9feac
Show file tree
Hide file tree
Showing 15 changed files with 811 additions and 507 deletions.
7 changes: 7 additions & 0 deletions contracts/LicenseToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,14 @@ contract LicenseToken is ILicenseToken, ERC721EnumerableUpgradeable, AccessManag
return _getLicenseTokenStorage().licenseTokenMetadatas[tokenId].expiresAt;
}

/// @notice Returns the canonical protocol-wide DisputeModule
/// @return The DisputeModule instance
function disputeModule() external view returns (IDisputeModule) {
return _getLicenseTokenStorage().disputeModule;
}

/// @notice Returns the canonical protocol-wide LicensingModule
/// @return The LicensingModule instance
function licensingModule() external view returns (ILicensingModule) {
return _getLicenseTokenStorage().licensingModule;
}
Expand Down
11 changes: 11 additions & 0 deletions contracts/interfaces/ILicenseToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ pragma solidity 0.8.23;
import { IERC721Metadata } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol";
import { IERC721Enumerable } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol";

import { IDisputeModule } from "./modules/dispute/IDisputeModule.sol";
import { ILicensingModule } from "./modules/licensing/ILicensingModule.sol";

/// @title ILicenseToken
/// @notice Interface for the License Token (ERC721) NFT collection that manages License Tokens representing
/// License Terms.
Expand Down Expand Up @@ -92,6 +95,14 @@ interface ILicenseToken is IERC721Metadata, IERC721Enumerable {
/// @return A `LicenseTokenMetadata` struct containing the metadata of the specified License Token.
function getLicenseTokenMetadata(uint256 tokenId) external view returns (LicenseTokenMetadata memory);

/// @notice Returns the canonical protocol-wide DisputeModule
/// @return The DisputeModule instance
function disputeModule() external view returns (IDisputeModule);

/// @notice Returns the canonical protocol-wide LicensingModule
/// @return The LicensingModule instance
function licensingModule() external view returns (ILicensingModule);

/// @notice Validates License Tokens for registering a derivative IP.
/// @dev This function checks if the License Tokens are valid for the derivative IP registration process.
/// for example, whether token is expired.
Expand Down
2 changes: 1 addition & 1 deletion contracts/lib/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ library Errors {
error LicenseRegistry__DerivativeAlreadyRegistered(address childIpId);
error LicenseRegistry__ParentIpTagged(address ipId);
error LicenseRegistry__DerivativeIsParent(address ipId);
error LicenseRegistry__ParentIpUnmachedLicenseTemplate(address ipId, address licenseTemplate);
error LicenseRegistry__ParentIpUnmatchedLicenseTemplate(address ipId, address licenseTemplate);
error LicenseRegistry__IndexOutOfBounds(address ipId, uint256 index, uint256 length);
error LicenseRegistry__LicenseTermsAlreadyAttached(address ipId, address licenseTemplate, uint256 licenseTermsId);
error LicenseRegistry__UnmatchedLicenseTemplate(address ipId, address licenseTemplate, address newLicenseTemplate);
Expand Down
2 changes: 1 addition & 1 deletion contracts/registries/LicenseRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,7 @@ contract LicenseRegistry is ILicenseRegistry, AccessManagedUpgradeable, UUPSUpgr
// childIp can only register with default license terms or the license terms attached to the parent IP
if ($.defaultLicenseTemplate != licenseTemplate || $.defaultLicenseTermsId != licenseTermsId) {
if ($.licenseTemplates[parentIpId] != licenseTemplate) {
revert Errors.LicenseRegistry__ParentIpUnmachedLicenseTemplate(parentIpId, licenseTemplate);
revert Errors.LicenseRegistry__ParentIpUnmatchedLicenseTemplate(parentIpId, licenseTemplate);
}
if (!$.attachedLicenseTerms[parentIpId].contains(licenseTermsId)) {
revert Errors.LicenseRegistry__ParentIpHasNoLicenseTerms(parentIpId, licenseTermsId);
Expand Down
16 changes: 8 additions & 8 deletions test/foundry/IPAccount.t.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;

import { IERC6551Account } from "erc6551/interfaces/IERC6551Account.sol";

import { IIPAccount } from "../../contracts/interfaces/IIPAccount.sol";
import { Errors } from "../../contracts/lib/Errors.sol";

Expand Down Expand Up @@ -41,9 +43,7 @@ contract IPAccountTest is BaseTest {
assertEq(predictedAccount, deployedAccount);
}

// TODO: Fix this test, "vm.addr(2)" hits error AccessController__BothCallerAndRecipientAreNotRegisteredModule
// but we want to test for "AccessController__PermissionDenied" for vm.addr(2) (which is not a module or IPAccount)
/*function test_IPAccount_TokenAndOwnership() public {
function test_IPAccount_TokenAndOwnership() public {
address owner = vm.addr(1);
uint256 tokenId = 100;

Expand All @@ -63,20 +63,20 @@ contract IPAccountTest is BaseTest {
abi.encodeWithSelector(
Errors.AccessController__PermissionDenied.selector,
address(ipAccount),
vm.addr(2),
address(module),
address(0),
bytes4(0)
)
);
ipAccount.isValidSigner(vm.addr(2), "");
ipAccount.isValidSigner(address(module), "");
assertEq(ipAccount.isValidSigner(owner, ""), IERC6551Account.isValidSigner.selector);

// Transfer token to new owner and make sure account owner changes
address newOwner = vm.addr(2);
address newOwner = address(module);
vm.prank(owner);
mockNFT.safeTransferFrom(owner, newOwner, tokenId);
mockNFT.transferFrom(owner, newOwner, tokenId);
assertEq(ipAccount.isValidSigner(newOwner, ""), IERC6551Account.isValidSigner.selector);
}*/
}

function test_IPAccount_OwnerExecutionPass() public {
address owner = vm.addr(1);
Expand Down
23 changes: 0 additions & 23 deletions test/foundry/IPAccount.tree

This file was deleted.

187 changes: 187 additions & 0 deletions test/foundry/IPAccountImpl.btt.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;

import { IERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
import { IERC721Receiver } from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import { IERC1155Receiver } from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol";
// import { SignatureChecker } from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
import { IERC6551Account } from "erc6551/interfaces/IERC6551Account.sol";

import { IIPAccount } from "../../contracts/interfaces/IIPAccount.sol";
import { IIPAccountStorage } from "../../contracts/interfaces/IIPAccountStorage.sol";
import { Errors } from "../../contracts/lib/Errors.sol";

import { MockModule } from "./mocks/module/MockModule.sol";
import { BaseTest } from "./utils/BaseTest.t.sol";

contract IPAccountImplBTT is BaseTest {
IIPAccount private ipAcct;
uint256 private chainId = block.chainid;
uint256 private tokenId = 55555;

address private ipOwner;
address private signer;
address private to;
bytes private data;

bytes private mockModuleDataSuccess = abi.encodeWithSignature("executeSuccessfully(string)", "success");
bytes private mockModuleDataRevert = abi.encodeWithSignature("executeRevert()");

function setUp() public override {
super.setUp();

ipOwner = u.alice;
mockNFT.mintId(ipOwner, tokenId);
ipAcct = IIPAccount(payable(ipAssetRegistry.registerIpAccount(chainId, address(mockNFT), tokenId)));
}

function test_IPAccountImpl_supportsInterface() public {
assertTrue(ipAcct.supportsInterface(type(IIPAccount).interfaceId));
assertTrue(ipAcct.supportsInterface(type(IIPAccountStorage).interfaceId));
assertTrue(ipAcct.supportsInterface(type(IERC6551Account).interfaceId));
assertTrue(ipAcct.supportsInterface(type(IERC721Receiver).interfaceId));
assertTrue(ipAcct.supportsInterface(type(IERC1155Receiver).interfaceId));
assertTrue(ipAcct.supportsInterface(type(IERC1155Receiver).interfaceId));
assertTrue(ipAcct.supportsInterface(type(IERC165).interfaceId));
}

function test_IPAccountImpl_token() public {
(uint256 chainId_, address tokenAddress_, uint256 tokenId_) = ipAcct.token();
assertEq(chainId_, chainId);
assertEq(tokenAddress_, address(mockNFT));
assertEq(tokenId_, tokenId);
}

modifier whenDataLenIsGT0AndLT4() {
data = "123";
_;
}

function test_IPAccountImpl_revert_isValidSigner_whenDataLenIsGT0AndLT4() public whenDataLenIsGT0AndLT4 {
signer = ipOwner;
vm.prank(signer);
vm.expectRevert(Errors.IPAccount__InvalidCalldata.selector);
assertEq(ipAcct.isValidSigner(signer, data), bytes4(0));
}

modifier whenDataLenIsZeroOrGTE4(bytes memory data_) {
require(data_.length == 0 || data_.length >= 4, "data length must be 0 or >= 4");
data = data_;
_;
}

modifier whenSignerIsNotOwner() {
signer = u.bob;
vm.startPrank(signer);
_;
}

function test_IPAccountImpl_revert_isValidSigner_inAccessControllerFail()
public
whenDataLenIsZeroOrGTE4("")
whenSignerIsNotOwner
{
vm.expectRevert(
abi.encodeWithSelector(
Errors.AccessController__BothCallerAndRecipientAreNotRegisteredModule.selector,
address(signer),
bytes4(0)
)
);
ipAcct.isValidSigner(signer, data);
}

modifier whenSignerIsOwner() {
signer = ipOwner;
vm.startPrank(signer);
_;
}

function test_IPAccountImpl_isValidSigner_inAccessControllerSucceed()
public
whenDataLenIsZeroOrGTE4("")
whenSignerIsOwner
{
assertEq(ipAcct.isValidSigner(signer, data), IERC6551Account.isValidSigner.selector);
}

modifier toIsRegisteredModule() {
vm.stopPrank();
vm.startPrank(u.admin);
MockModule mockModule = new MockModule(address(ipAssetRegistry), address(moduleRegistry), "MockModule");
moduleRegistry.registerModule(mockModule.name(), address(mockModule));
to = address(mockModule);
vm.startPrank(signer); // back to the original prank
_;
}

function test_IPAccountImpl_execute_revert_invalidSigner()
public
whenDataLenIsZeroOrGTE4(mockModuleDataSuccess)
whenSignerIsNotOwner
toIsRegisteredModule
{
vm.expectRevert(
abi.encodeWithSelector(
Errors.AccessController__PermissionDenied.selector,
address(ipAcct),
signer,
to,
MockModule.executeSuccessfully.selector
)
);
ipAcct.execute(to, 0, data);
}

function test_IPAccountImpl_execute_resultSuccess()
public
whenDataLenIsZeroOrGTE4(mockModuleDataSuccess)
whenSignerIsOwner
toIsRegisteredModule
{
uint256 expectedState = ipAcct.state() + 1;

vm.expectEmit(address(ipAcct));
emit IIPAccount.Executed(to, 0, data, expectedState);
bytes memory result = ipAcct.execute(to, 0, data);

assertEq(abi.decode(result, (string)), "success");
assertEq(ipAcct.state(), expectedState);
}

function test_IPAccountImpl_execute_resultRevert()
public
whenDataLenIsZeroOrGTE4(mockModuleDataRevert)
whenSignerIsOwner
toIsRegisteredModule
{
uint256 expectedState = ipAcct.state();

vm.expectRevert("MockModule: executeRevert");
ipAcct.execute(to, 0, data);

assertEq(ipAcct.state(), expectedState);
}

function test_IPAccountImpl_receiveERC721() public {
assertEq(
IERC721Receiver(address(ipAcct)).onERC721Received(ipOwner, address(0), 111, ""),
IERC721Receiver.onERC721Received.selector
);
}

function test_IPAccountImpl_receiveERC1155() public {
assertEq(
IERC1155Receiver(address(ipAcct)).onERC1155Received(ipOwner, address(0), 111, 1, ""),
IERC1155Receiver.onERC1155Received.selector
);

uint256[] memory ids = new uint256[](1);
uint256[] memory values = new uint256[](1);

assertEq(
IERC1155Receiver(address(ipAcct)).onERC1155BatchReceived(ipOwner, address(0), ids, values, ""),
IERC1155Receiver.onERC1155BatchReceived.selector
);
}
}
48 changes: 48 additions & 0 deletions test/foundry/IPAccountImpl.tree
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
IPAccountImpl.sol
# when checking supported interfaces
## it should support IIPAccount interface
## it should support IIPAccountStorage interface
## it should support IERC6551Account interface
## it should support IERC1155Receiver interface
## it should support IERC721Receiver interface
## it should support IERC165 interface
# when checking identifier of IP
## it should return expected chain ID
## it should return expected token contract
## it should return expected token ID
# when checking if signer is valid
## when the data length is greater than zero and less than four
### it should revert
## when the data length is zero or greater than or equal to four
### when checking permission via access controller fails
#### it should revert
### when checking permission via access controller succeeds
#### it should true
#### it sohuld return IERC6551Account.isValidSigner.selector
# when executing
## when signer is invalid in access controlelr
### it should revert
## when signer is valid in access controller
### when call fails
#### it should revert
### when call succeeds
#### it should increment `state`
#### it should emit an event
#### it should return result
# when executing with signature
## given the signer is zero address
### it should revert
## given the deadline is in the past
### it should revert
## given the EIP1976 signature is invalid now
### it should revert
## given the EIP1976 signature is valid
### it should call `_execute`
### it should emit an event
# when receiving ERC721
## it should return onERC721Received selector
# when receiving ERC1155
## given batch received
### it should return onERC1155BatchReceived selector
## given single received
### it should return onERC1155Received selector
Loading

0 comments on commit 0a9feac

Please sign in to comment.