From c71b9f3ad25a6fd72d61a7856b0ca6bb0183cc7c Mon Sep 17 00:00:00 2001 From: b00ste Date: Thu, 22 Aug 2024 14:46:30 +0300 Subject: [PATCH] test: add extreme case tests --- .../contracts/mock/InfiniteLoopURD.sol | 24 +++++ .../contracts/mock/ReturnBomb.sol | 26 +++++ .../mock/SelfDestructOnInterfaceCheck.sol | 23 +++++ .../tests/LSP26FollowerSystem.test.ts | 96 +++++++++++++++++++ 4 files changed, 169 insertions(+) create mode 100644 packages/lsp26-contracts/contracts/mock/InfiniteLoopURD.sol create mode 100644 packages/lsp26-contracts/contracts/mock/ReturnBomb.sol create mode 100644 packages/lsp26-contracts/contracts/mock/SelfDestructOnInterfaceCheck.sol diff --git a/packages/lsp26-contracts/contracts/mock/InfiniteLoopURD.sol b/packages/lsp26-contracts/contracts/mock/InfiniteLoopURD.sol new file mode 100644 index 000000000..8517a6241 --- /dev/null +++ b/packages/lsp26-contracts/contracts/mock/InfiniteLoopURD.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +// interfaces +import { + ILSP1UniversalReceiver +} from "@lukso/lsp1-contracts/contracts/ILSP1UniversalReceiver.sol"; + +contract InfiniteLoopURD is ILSP1UniversalReceiver { + uint256 public counter; + + function supportsInterface(bytes4) external pure returns (bool) { + return true; + } + + function universalReceiver( + bytes32, + bytes memory + ) external payable returns (bytes memory) { + while (true) { + ++counter; + } + } +} diff --git a/packages/lsp26-contracts/contracts/mock/ReturnBomb.sol b/packages/lsp26-contracts/contracts/mock/ReturnBomb.sol new file mode 100644 index 000000000..7212088da --- /dev/null +++ b/packages/lsp26-contracts/contracts/mock/ReturnBomb.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +// interfaces +import { + ILSP1UniversalReceiver +} from "@lukso/lsp1-contracts/contracts/ILSP1UniversalReceiver.sol"; + +contract ReturnBomb is ILSP1UniversalReceiver { + uint256 public counter; + + function supportsInterface(bytes4) external pure returns (bool) { + return true; + } + + function universalReceiver( + bytes32, + bytes memory + ) external payable returns (bytes memory) { + ++counter; + // solhint-disable-next-line no-inline-assembly + assembly { + revert(0, 10000) + } + } +} diff --git a/packages/lsp26-contracts/contracts/mock/SelfDestructOnInterfaceCheck.sol b/packages/lsp26-contracts/contracts/mock/SelfDestructOnInterfaceCheck.sol new file mode 100644 index 000000000..517ad6f82 --- /dev/null +++ b/packages/lsp26-contracts/contracts/mock/SelfDestructOnInterfaceCheck.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +// interfaces +import { + ILSP1UniversalReceiver +} from "@lukso/lsp1-contracts/contracts/ILSP1UniversalReceiver.sol"; + +contract SelfDestructOnInterfaceCheck is ILSP1UniversalReceiver { + uint256 public counter; + + function supportsInterface(bytes4) external returns (bool) { + selfdestruct(payable(msg.sender)); + return true; + } + + function universalReceiver( + bytes32, + bytes memory + ) external payable returns (bytes memory) { + return ""; + } +} diff --git a/packages/lsp26-contracts/tests/LSP26FollowerSystem.test.ts b/packages/lsp26-contracts/tests/LSP26FollowerSystem.test.ts index fe21b6d22..ef00ff770 100644 --- a/packages/lsp26-contracts/tests/LSP26FollowerSystem.test.ts +++ b/packages/lsp26-contracts/tests/LSP26FollowerSystem.test.ts @@ -15,6 +15,12 @@ import { LSP0ERC725Account__factory, RevertOnFollow__factory, RevertOnFollow, + ReturnBomb__factory, + ReturnBomb, + SelfDestructOnInterfaceCheck__factory, + SelfDestructOnInterfaceCheck, + InfiniteLoopURD, + InfiniteLoopURD__factory, } from '../types'; describe('testing `LSP26FollowerSystem`', () => { @@ -115,6 +121,96 @@ describe('testing `LSP26FollowerSystem`', () => { }); }); + describe('testing follow/unfollow a contract that self destructs on interface check', async () => { + let selfDestruct: SelfDestructOnInterfaceCheck; + + before(async () => { + selfDestruct = await new SelfDestructOnInterfaceCheck__factory(context.owner).deploy(); + }); + + it('should pass following', async () => { + await context.followerSystem.connect(context.owner).follow(await selfDestruct.getAddress()); + + expect( + await context.followerSystem.isFollowing( + context.owner.address, + await selfDestruct.getAddress(), + ), + ).to.be.true; + }); + + it('should pass unfollowing', async () => { + await context.followerSystem.connect(context.owner).unfollow(await selfDestruct.getAddress()); + + expect( + await context.followerSystem.isFollowing( + context.owner.address, + await selfDestruct.getAddress(), + ), + ).to.be.false; + }); + }); + + describe('testing follow/unfollow a contract with return bomb', () => { + let returnBomb: ReturnBomb; + + before(async () => { + returnBomb = await new ReturnBomb__factory(context.owner).deploy(); + }); + + it('should pass following', async () => { + await context.followerSystem.connect(context.owner).follow(await returnBomb.getAddress()); + + expect( + await context.followerSystem.isFollowing( + context.owner.address, + await returnBomb.getAddress(), + ), + ).to.be.true; + }); + + it('should pass unfollowing', async () => { + await context.followerSystem.connect(context.owner).unfollow(await returnBomb.getAddress()); + + expect( + await context.followerSystem.isFollowing( + context.owner.address, + await returnBomb.getAddress(), + ), + ).to.be.false; + }); + }); + + describe('testing follow/unfollow a contract that has an infinite loop in urd', () => { + let infiniteLoop: InfiniteLoopURD; + + before(async () => { + infiniteLoop = await new InfiniteLoopURD__factory(context.owner).deploy(); + }); + + it('should pass following', async () => { + await context.followerSystem.connect(context.owner).follow(await infiniteLoop.getAddress()); + + expect( + await context.followerSystem.isFollowing( + context.owner.address, + await infiniteLoop.getAddress(), + ), + ).to.be.true; + }); + + it('should pass unfollowing', async () => { + await context.followerSystem.connect(context.owner).unfollow(await infiniteLoop.getAddress()); + + expect( + await context.followerSystem.isFollowing( + context.owner.address, + await infiniteLoop.getAddress(), + ), + ).to.be.false; + }); + }); + describe.skip('gas tests', () => { const gasCostResult: { followingGasCost?: number[];