Skip to content

Commit

Permalink
feat: add tests, remove the dependecy from core (using mocks)
Browse files Browse the repository at this point in the history
  • Loading branch information
Andres Adjimann committed Aug 16, 2024
1 parent f1abe4b commit 85e4a44
Show file tree
Hide file tree
Showing 21 changed files with 2,127 additions and 1,254 deletions.
15 changes: 0 additions & 15 deletions packages/avatar/contracts/mocks/FakePolygonSand.sol

This file was deleted.

77 changes: 75 additions & 2 deletions packages/avatar/contracts/mocks/MockERC20.sol
Original file line number Diff line number Diff line change
@@ -1,10 +1,83 @@
// SPDX-License-Identifier: MIT
// solhint-disable one-contract-per-file
pragma solidity 0.8.15;

import {Ownable} from "@openzeppelin/contracts-0.8.15/access/Ownable.sol";
import {ERC20} from "@openzeppelin/contracts-0.8.15/token/ERC20/ERC20.sol";

contract MockERC20 is ERC20 {
constructor(string memory _name, string memory _symbol, uint256 _initialSupply) ERC20(_name, _symbol) {
contract MockERC20 is ERC20, Ownable {

constructor(uint256 _initialSupply) ERC20("MOCKTOKEN", "MOCK") {
_mint(msg.sender, _initialSupply * 1e18);
}

function donateTo(address recipient, uint256 amount) external onlyOwner {
_mint(recipient, amount);
}

/// @dev instead of using approve and call we use this method directly for testing.
function mint(
MintInterface target,
address _wallet,
uint256 _amount,
uint256 _signatureId,
bytes calldata _signature
) external {
target.mint(_wallet, _amount, _signatureId, _signature);
}
/// @notice Approve `target` to spend `amount` and call it with data.
/// @param target The address to be given rights to transfer and destination of the call.
/// @param amount The number of tokens allowed.
/// @param data The bytes for the call.
/// @return The data of the call.
function approveAndCall(
address target,
uint256 amount,
bytes calldata data
) external payable returns (bytes memory) {
require(doFirstParamEqualsAddress(data, _msgSender()), "FIRST_PARAM_NOT_SENDER");

_approve(_msgSender(), target, amount);

// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returnData) = target.call{value : msg.value}(data);
if (success) {
return returnData;
}
if (returnData.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returnData)
revert(add(32, returnData), returndata_size)
}
} else {
revert("Empty error from destination");
}
}

function doFirstParamEqualsAddress(bytes memory data, address _address)
internal
pure
returns (bool)
{
if (data.length < (36 + 32)) {
return false;
}
uint256 value;
assembly {
value := mload(add(data, 36))
}
return value == uint160(_address);
}

}

interface MintInterface {
function mint(
address _wallet,
uint256 _amount,
uint256 _signatureId,
bytes calldata _signature
) external;
}
99 changes: 99 additions & 0 deletions packages/avatar/contracts/mocks/MockOperatorFilterRegistry.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

import {Ownable} from "@openzeppelin/contracts-0.8.15/access/Ownable.sol";

contract MockOperatorFilterRegistry {
/// @notice Emitted when a registration is updated.
event RegistrationUpdated(address indexed registrant, bool indexed registered);

/// @notice Emitted when the caller is not the address or EIP-173 "owner()"
error OnlyAddressOrOwner();
/// @notice Emitted when trying to register and the contract is not ownable (EIP-173 "owner()")
error NotOwnable();
/// @notice Emitted when the registrant is already registered.
error AlreadyRegistered();
/// @notice Emitted when an address is filtered.
error AddressFiltered(address filtered);
/// @notice Emitted when a codeHash is filtered.
error CodeHashFiltered(address account, bytes32 codeHash);

mapping(address => address) private _registrations;

bool public revertWithAddressFiltered;

bool public revertWithCodeHashFiltered;
/**
* @notice Restricts method caller to the address or EIP-173 "owner()"
*/
modifier onlyAddressOrOwner(address addr) {
if (msg.sender != addr) {
try Ownable(addr).owner() returns (address owner) {
if (msg.sender != owner) {
revert OnlyAddressOrOwner();
}
} catch (bytes memory reason) {
if (reason.length == 0) {
revert NotOwnable();
} else {
/// @solidity memory-safe-assembly
assembly {
revert(add(32, reason), mload(reason))
}
}
}
}
_;
}

function doRevert(bool _revertWithAddressFiltered, bool _revertWithCodeHashFiltered) external {
revertWithAddressFiltered = _revertWithAddressFiltered;
revertWithCodeHashFiltered = _revertWithCodeHashFiltered;
}

/**
* @notice Registers an address with the registry. May be called by address itself or by EIP-173 owner.
*/
function register(address registrant) external onlyAddressOrOwner(registrant) {
if (_registrations[registrant] != address(0)) {
revert AlreadyRegistered();
}
_registrations[registrant] = registrant;
emit RegistrationUpdated(registrant, true);
}

/**
* @notice Returns true if an address has registered
*/
function isRegistered(address registrant) external view returns (bool) {
return _registrations[registrant] != address(0);
}

/**
* @notice Returns true if operator is not filtered for a given token, either by address or codeHash. Also returns
* true if supplied registrant address is not registered.
* Note that this method will *revert* if an operator or its codehash is filtered with an error that is
* more informational than a false boolean, so smart contracts that query this method for informational
* purposes will need to wrap in a try/catch or perform a low-level staticcall in order to handle the case
* that an operator is filtered.
*/
function isOperatorAllowed(address registrant, address operator) external view returns (bool) {
address registration = _registrations[registrant];
if (registration != address(0)) {
if (revertWithAddressFiltered) {
revert AddressFiltered(operator);
} else if (revertWithCodeHashFiltered) {
bytes32 codeHash = operator.codehash;
revert CodeHashFiltered(operator, codeHash);
}
}
return true;
}

/**
* @dev Convenience method to compute the code hash of an arbitrary contract
*/
function codeHashOf(address a) external view returns (bytes32) {
return a.codehash;
}
}
14 changes: 7 additions & 7 deletions packages/avatar/contracts/nft-collection/NFTCollection.sol
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,7 @@ IERC4906
require(_wallet != address(0), "NFTCollection: wallet is zero address");
require(_amount > 0, "NFTCollection: amount cannot be 0");

_checkAndSetSignature({_address : _wallet, _signatureId : _signatureId, _signature : _signature});
_checkAndSetSignature({_wallet : _wallet, _signatureId : _signatureId, _signature : _signature});

require(_checkWaveNotComplete(_amount), "NFTCollection: wave completed");
require(_checkLimitPerWalletNotReached(_wallet, _amount), "NFTCollection: max allowed");
Expand Down Expand Up @@ -524,8 +524,8 @@ IERC4906
uint256 amount = wallets[i].amount;
require(amount > 0, "NFTCollection: amount cannot be 0");
require(_checkWaveNotComplete(amount), "NFTCollection: wave completed");
require(_checkTotalNotReached(amount), "NFTCollection: max reached");
require(_checkLimitPerWalletNotReached(wallet, amount), "NFTCollection: max allowed");
require(_checkTotalNotReached(amount), "NFTCollection: max reached");

for (uint256 j; j < amount; j++) {
// @dev _mint already checks the destination address
Expand Down Expand Up @@ -556,7 +556,7 @@ IERC4906
address sender = _msgSender();
require(ownerOf(_tokenId) == sender, "NFTCollection: sender is not owner");

_checkAndSetSignature({_address : sender, _signatureId : _signatureId, _signature : _signature});
_checkAndSetSignature({_wallet : sender, _signatureId : _signatureId, _signature : _signature});

emit MetadataUpdate(_tokenId);
}
Expand Down Expand Up @@ -740,9 +740,9 @@ IERC4906
* @notice This function is used to register Land contract on the Operator Filterer Registry of Opensea.
* @param subscriptionOrRegistrantToCopy registration address of the list to subscribe.
* @param subscribe bool to signify subscription 'true' or to copy the list 'false'.
* @dev subscriptionOrRegistrantToCopy == address(0), just register
*/
function register(address subscriptionOrRegistrantToCopy, bool subscribe) external onlyOwner {
require(subscriptionOrRegistrantToCopy != address(0), "invalid address");
_register(subscriptionOrRegistrantToCopy, subscribe);
}

Expand Down Expand Up @@ -1006,18 +1006,18 @@ IERC4906
/**
* @notice checks that the provided signature is valid, while also taking into
* consideration the provided address and signatureId.
* @param _address address to be used in validating the signature
* @param _wallet address to be used in validating the signature
* @param _signatureId signing signature ID
* @param _signature signing signature value
*/
function _checkAndSetSignature(
address _address,
address _wallet,
uint256 _signatureId,
bytes calldata _signature
) internal {
require(_signatureIds[_signatureId] == 0, "NFTCollection: signatureId already used");
require(
_checkSignature(_address, _signatureId, address(this), block.chainid, _signature) == signAddress,
_checkSignature(_wallet, _signatureId, address(this), block.chainid, _signature) == signAddress,
"NFTCollection: signature failed"
);
_signatureIds[_signatureId] = 1;
Expand Down
1 change: 0 additions & 1 deletion packages/avatar/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@
"@openzeppelin/contracts-upgradeable": "^4.9.2",
"@openzeppelin/contracts-upgradeable-0.8.13": "npm:@openzeppelin/[email protected]",
"@release-it/keep-a-changelog": "^5.0.0",
"@sandbox-smart-contracts/core": "0.0.1",
"@typechain/ethers-v6": "^0.4.0",
"@typechain/hardhat": "^8.0.0",
"@types/chai": "^4.3.5",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/dist/src/signer-with-
import {ethers, network} from 'hardhat';
import {Wallet, parseUnits} from 'ethers';
import {AvatarCollection} from '../../../typechain-types/contracts/avatar/AvatarCollection';
import {FakePolygonSand, PolygonSand} from '../../../typechain-types';
import {getTestingAccounts, topUpAddressWithETH} from '../fixtures';
import {setupAvatarCollectionContract} from '../collectionSetup';
import {MockERC20} from '../../../typechain-types';

export const collectionName = 'MockAvatarTesting';
export const COLLECTION_MAX_SUPPLY = 500;
Expand Down Expand Up @@ -198,7 +198,7 @@ function signAuthMessageAs(
return wallet.signMessage(ethers.getBytes(ethers.keccak256(hashedData)));
}

async function setupTransferSand(sandContractAsOwner: FakePolygonSand) {
async function setupTransferSand(sandContractAsOwner: MockERC20) {
return async (address: string, amount: string) => {
const amountToSend = parseUnits(amount.toString(), 'ether');
await sandContractAsOwner.donateTo(address, amountToSend);
Expand All @@ -207,7 +207,7 @@ async function setupTransferSand(sandContractAsOwner: FakePolygonSand) {

function mintSetup(
collectionContract: AvatarCollection,
sandContract: PolygonSand
sandContract: MockERC20
) {
return async (
wallet: Wallet | SignerWithAddress | HardhatEthersSigner,
Expand Down
12 changes: 4 additions & 8 deletions packages/avatar/test/avatar/Raffle/raffle.fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,12 @@ import {assert} from 'chai';
import {HardhatEthersSigner} from '@nomicfoundation/hardhat-ethers/signers';
import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/dist/src/signer-with-address';
import {ethers, network} from 'hardhat';
import {Wallet, parseUnits} from 'ethers';
import {
FakePolygonSand,
GenericRaffle,
PolygonSand,
} from '../../../typechain-types';
import {parseUnits, Wallet} from 'ethers';
import {GenericRaffle, MockERC20, PolygonSand} from '../../../typechain-types';
import {
deployFakeSandContract,
getTestingAccounts,
topUpAddressWithETH,
deployFakeSandContract,
} from '../fixtures';
import {setupRaffleContract} from '../raffleSetup';

Expand Down Expand Up @@ -166,7 +162,7 @@ function signAuthMessageAs(
return wallet.signMessage(ethers.getBytes(ethers.keccak256(hashedData)));
}

async function setupTransferSand(sandContractAsOwner: FakePolygonSand) {
async function setupTransferSand(sandContractAsOwner: MockERC20) {
return async (address: string, amount: string) => {
const amountToSend = parseUnits(amount.toString(), 'ether');
await sandContractAsOwner.donateTo(address, amountToSend);
Expand Down
9 changes: 3 additions & 6 deletions packages/avatar/test/avatar/RaffleOld/raffleold.fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import {HardhatEthersSigner} from '@nomicfoundation/hardhat-ethers/signers';
import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/dist/src/signer-with-address';
import {ethers, network} from 'hardhat';
import {Wallet, ZeroAddress, parseUnits} from 'ethers';
import {FakePolygonSand, PolygonSand} from '../../../typechain-types';
import {GenericRaffle} from '../../../typechain-types/contracts/raffleold/contracts/GenericRaffle';
import {
getTestingAccounts,
topUpAddressWithETH,
deployFakeSandContract,
} from '../fixtures';
import {setupRaffleContract} from '../raffleSetup';
import {MockERC20} from '../../../typechain-types';

export const preSetupAvatar = async (
contractName: string,
Expand Down Expand Up @@ -176,17 +176,14 @@ function signAuthMessageAs(
return wallet.signMessage(ethers.getBytes(ethers.keccak256(hashedData)));
}

async function setupTransferSand(sandContractAsOwner: FakePolygonSand) {
async function setupTransferSand(sandContractAsOwner: MockERC20) {
return async (address: string, amount: string) => {
const amountToSend = parseUnits(amount.toString(), 'ether');
await sandContractAsOwner.donateTo(address, amountToSend);
};
}

function mintSetup(
collectionContract: GenericRaffle,
sandContract: PolygonSand
) {
function mintSetup(collectionContract: GenericRaffle, sandContract: MockERC20) {
return async (
wallet: Wallet | SignerWithAddress | HardhatEthersSigner,
address: string,
Expand Down
Loading

1 comment on commit 85e4a44

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage for this commit

85.20%

Coverage Report
FileStmtsBranchesFuncsLinesUncovered Lines
packages/avatar/contracts/avatar
   AvatarCollection.sol74.25%56.56%73.81%90.37%285, 329–337, 339–340, 345, 403–407, 425, 442, 459, 483–487, 491–492, 495, 526, 537, 545, 545–546, 565, 599, 621, 631, 712, 732, 745, 745, 745, 763, 763, 765, 774, 774, 776, 786, 786–787, 797, 797–798, 809, 809–810, 823, 832, 861–862, 992–993, 996, 999
   CollectionAccessControl.sol60.87%53.33%63.64%67.86%120, 130–131, 140, 150–151, 161–162, 162, 162, 164–165, 167, 173, 173–174, 185, 192, 192–194, 98–99
   ERC721BurnMemoryEnumerableUpgradeable.sol93.33%75%100%100%62, 75
packages/avatar/contracts/common
   IERC4906.sol100%100%100%100%
   IERC5313.sol100%100%100%100%
packages/avatar/contracts/common/BaseWithStorage/ERC2771
   ERC2771HandlerUpgradeable.sol44.44%25%60%44.44%23, 27, 30, 39, 39, 39–40, 42
packages/avatar/contracts/common/OperatorFilterer
   IOperatorFilterRegistry.sol100%100%100%100%
   UpdatableOperatorFiltererUpgradeable.sol11.11%10%25%9.52%18, 23–24, 24, 24–25, 27, 27, 27–28, 30, 38, 38, 38, 42, 42, 42–44, 46, 46, 46–47, 50, 55, 55, 55–56, 56, 56–57, 60, 68, 68, 68–69, 71
packages/avatar/contracts/nft-collection
   ERC2771HandlerUpgradeable.sol55.56%25%71.43%56.25%48, 60–61, 74–76, 76, 76–77, 79
   ERC721BurnMemoryUpgradeable.sol96.77%87.50%100%100%79
   NFTCollection.sol92.10%90.63%86.79%95.45%330, 370, 485, 517, 667–668, 678–679, 691, 745, 842, 844, 865–866, 876–877, 888–889, 930, 951, 982, 990
   UpdatableOperatorFiltererUpgradeable.sol68.09%50%83.33%76%103–106, 108–109, 140, 140, 140–141, 89, 89–91
packages/avatar/contracts/proxy
   CollectionFactory.sol86.59%89.06%75%87.50%149, 230, 293, 362–366, 368, 385, 402, 410, 410–411, 423, 423–424
   CollectionProxy.sol88.24%50%100%100%55, 70
packages/avatar/contracts/raffle
   DanceFight.sol75%50%100%100%20
   FistOfTheNorthStar.sol75%50%100%100%20
   GenericRaffle.sol67.92%52.22%62.07%84.95%178, 189–195, 225–229, 252–259, 266–267, 270, 288, 288–289, 289, 289–290, 308, 381, 390, 409, 409–410, 419, 419, 421, 430, 430, 432, 442, 442–443, 453, 453–454, 465, 465–466, 566, 575, 626–627, 630, 633
   HellsKitchen.sol75%50%100%100%20
   MadBalls.sol75%50%100%100%20
   ParisHilton.sol75%50%100%100%20
   Rabbids.sol75%50%100%100%20

Please sign in to comment.