Skip to content

Commit

Permalink
Add get getSellersCollectionsPaginated method (#824)
Browse files Browse the repository at this point in the history
* Solidity code for getSellersCollectionsPaginated

* Add getSellersCollectionsPaginated() test cases

* fix typo

* add getSellersCollectionCount method

* getSellersCollectionCount unit tests
  • Loading branch information
zajck committed Nov 30, 2023
1 parent ed1ee1e commit 25fa3cf
Show file tree
Hide file tree
Showing 4 changed files with 199 additions and 7 deletions.
29 changes: 27 additions & 2 deletions contracts/interfaces/handlers/IBosonAccountHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { IBosonAccountEvents } from "../events/IBosonAccountEvents.sol";
*
* @notice Handles creation, update, retrieval of accounts within the protocol.
*
* The ERC-165 identifier for this interface is: 0x890d5d20
* The ERC-165 identifier for this interface is: 0x079a9d3b
*/
interface IBosonAccountHandler is IBosonAccountEvents, BosonErrors {
/**
Expand Down Expand Up @@ -450,7 +450,8 @@ interface IBosonAccountHandler is IBosonAccountEvents, BosonErrors {
) external view returns (bool exists, BosonTypes.Seller memory seller, BosonTypes.AuthToken memory authToken);

/**
* @notice Gets the details about a seller's collections.
* @notice Gets the details about all seller's collections.
* In case seller has too many collections and this runs out of gas, please use getSellersCollectionsPaginated.
*
* @param _sellerId - the id of the seller to check
* @return defaultVoucherAddress - the address of the default voucher contract for the seller
Expand All @@ -460,6 +461,30 @@ interface IBosonAccountHandler is IBosonAccountEvents, BosonErrors {
uint256 _sellerId
) external view returns (address defaultVoucherAddress, BosonTypes.Collection[] memory additionalCollections);

/**
* @notice Gets the details about all seller's collections.
* Use getSellersCollectionCount to get the total number of collections.
*
* @param _sellerId - the id of the seller to check
* @param _limit - the maximum number of Collections that should be returned starting from the index defined by `_offset`. If `_offset` + `_limit` exceeds total number of collections, `_limit` is adjusted to return all remaining collections.
* @param _offset - the starting index from which to return collections. If `_offset` is greater than or equal to total number of collections, an empty list is returned.
* @return defaultVoucherAddress - the address of the default voucher contract for the seller
* @return additionalCollections - an array of additional collections that the seller has created
*/
function getSellersCollectionsPaginated(
uint256 _sellerId,
uint256 _limit,
uint256 _offset
) external view returns (address defaultVoucherAddress, BosonTypes.Collection[] memory additionalCollections);

/**
* @notice Returns the number of additional collections for a seller.
* Use this in conjunction with getSellersCollectionsPaginated to get all collections.
*
* @param _sellerId - the id of the seller to check
*/
function getSellersCollectionCount(uint256 _sellerId) external view returns (uint256 collectionCount);

/**
* @notice Returns the availability of salt for a seller.
*
Expand Down
9 changes: 5 additions & 4 deletions contracts/protocol/facets/FundsHandlerFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -179,16 +179,17 @@ contract FundsHandlerFacet is IBosonFundsHandler, ProtocolBase {
uint256 _offset
) external view override returns (address[] memory tokenList) {
address[] storage tokens = protocolLookups().tokenList[_entityId];
uint256 tokenCount = tokens.length;

if (_offset >= tokens.length) {
if (_offset >= tokenCount) {
return new address[](0);
} else if (_offset + _limit > tokens.length) {
_limit = tokens.length - _offset;
} else if (_offset + _limit > tokenCount) {
_limit = tokenCount - _offset;
}

tokenList = new address[](_limit);

for (uint i = 0; i < _limit; ) {
for (uint256 i = 0; i < _limit; ) {
tokenList[i] = tokens[_offset++];

unchecked {
Expand Down
50 changes: 49 additions & 1 deletion contracts/protocol/facets/SellerHandlerFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -717,7 +717,8 @@ contract SellerHandlerFacet is SellerBase {
}

/**
* @notice Gets the details about a seller's collections.
* @notice Gets the details about all seller's collections.
* In case seller has too many collections and this runs out of gas, please use getSellersCollectionsPaginated.
*
* @param _sellerId - the id of the seller to check
* @return defaultVoucherAddress - the address of the default voucher contract for the seller
Expand All @@ -730,6 +731,53 @@ contract SellerHandlerFacet is SellerBase {
return (pl.cloneAddress[_sellerId], pl.additionalCollections[_sellerId]);
}

/**
* @notice Gets the details about all seller's collections.
* Use getSellersCollectionCount to get the total number of collections.
*
* @param _sellerId - the id of the seller to check
* @param _limit - the maximum number of Collections that should be returned starting from the index defined by `_offset`. If `_offset` + `_limit` exceeds total number of collections, `_limit` is adjusted to return all remaining collections.
* @param _offset - the starting index from which to return collections. If `_offset` is greater than or equal to total number of collections, an empty list is returned.
* @return defaultVoucherAddress - the address of the default voucher contract for the seller
* @return additionalCollections - an array of additional collections that the seller has created
*/
function getSellersCollectionsPaginated(
uint256 _sellerId,
uint256 _limit,
uint256 _offset
) external view returns (address defaultVoucherAddress, Collection[] memory additionalCollections) {
ProtocolLib.ProtocolLookups storage pl = protocolLookups();
Collection[] storage sellersAdditionalCollections = pl.additionalCollections[_sellerId];
uint256 collectionCount = sellersAdditionalCollections.length;

if (_offset >= collectionCount) {
return (pl.cloneAddress[_sellerId], new Collection[](0));
} else if (_offset + _limit > collectionCount) {
_limit = collectionCount - _offset;
}

additionalCollections = new Collection[](_limit);

for (uint256 i = 0; i < _limit; ) {
additionalCollections[i] = sellersAdditionalCollections[_offset++];
unchecked {
i++;
}
}

return (pl.cloneAddress[_sellerId], additionalCollections);
}

/**
* @notice Returns the number of additional collections for a seller.
* Use this in conjunction with getSellersCollectionsPaginated to get all collections.
*
* @param _sellerId - the id of the seller to check
*/
function getSellersCollectionCount(uint256 _sellerId) external view returns (uint256 collectionCount) {
return protocolLookups().additionalCollections[_sellerId].length;
}

/**
* @notice Returns the availability of salt for a seller.
*
Expand Down
118 changes: 118 additions & 0 deletions test/protocol/SellerHandlerTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -3386,6 +3386,124 @@ describe("SellerHandler", function () {
});
});

context("👉 getSellersCollectionsPaginated()", async function () {
let externalId, expectedDefaultAddress, expectedCollectionAddress, additionalCollections;

beforeEach(async function () {
// Create a seller
await accountHandler.connect(admin).createSeller(seller, emptyAuthToken, voucherInitValues);

expectedDefaultAddress = calculateCloneAddress(
await accountHandler.getAddress(),
beaconProxyAddress,
admin.address
); // default

additionalCollections = new CollectionList([]);
for (let i = 1; i <= 5; i++) {
externalId = `Brand${i}`;
voucherInitValues.collectionSalt = encodeBytes32String(externalId);
expectedCollectionAddress = calculateCloneAddress(
await accountHandler.getAddress(),
beaconProxyAddress,
admin.address,
voucherInitValues.collectionSalt
);
voucherInitValues.contractURI = `https://brand${i}.com`;

// Create a new collection
await accountHandler.connect(assistant).createNewCollection(externalId, voucherInitValues);

// Add to expected collections
additionalCollections.collections.push(new Collection(expectedCollectionAddress, externalId));
}
});

it("should return correct collection list", async function () {
const limit = 3;
const offset = 1;

const expectedCollections = new CollectionList(additionalCollections.collections.slice(offset, offset + limit));

const [defaultVoucherAddress, collections] = await accountHandler
.connect(rando)
.getSellersCollectionsPaginated(seller.id, limit, offset);
const returnedCollections = CollectionList.fromStruct(collections);

expect(defaultVoucherAddress).to.equal(expectedDefaultAddress, "Wrong default voucher address");
expect(returnedCollections).to.deep.equal(expectedCollections, "Wrong additional collections");
});

it("Offset is more than number of collections", async function () {
const limit = 2;
const offset = 8;

const expectedCollections = new CollectionList([]); // empty

const [defaultVoucherAddress, collections] = await accountHandler
.connect(rando)
.getSellersCollectionsPaginated(seller.id, limit, offset);
const returnedCollections = CollectionList.fromStruct(collections);

expect(defaultVoucherAddress).to.equal(expectedDefaultAddress, "Wrong default voucher address");
expect(returnedCollections).to.deep.equal(expectedCollections, "Wrong additional collections");
});

it("Limit + offset is more than number of collections", async function () {
const limit = 7;
const offset = 2;

const expectedCollections = new CollectionList(additionalCollections.collections.slice(offset)); // everything after offset

const [defaultVoucherAddress, collections] = await accountHandler
.connect(rando)
.getSellersCollectionsPaginated(seller.id, limit, offset);
const returnedCollections = CollectionList.fromStruct(collections);

expect(defaultVoucherAddress).to.equal(expectedDefaultAddress, "Wrong default voucher address");
expect(returnedCollections).to.deep.equal(expectedCollections, "Wrong additional collections");
});
});

context("👉 getSellersCollectionCount()", async function () {
beforeEach(async function () {
// Create a seller
await accountHandler.connect(admin).createSeller(seller, emptyAuthToken, voucherInitValues);
});

it("seller has no additional collections", async function () {
const expectedCollectionCount = 0;
const sellersCollectionCount = await accountHandler.connect(rando).getSellersCollectionCount(seller.id);

expect(sellersCollectionCount).to.equal(expectedCollectionCount, "Incorrect number of collections");
});

it("seller has additional collections", async function () {
const expectedCollectionCount = 4;

for (let i = 1; i <= expectedCollectionCount; i++) {
const externalId = `Brand${i}`;
voucherInitValues.collectionSalt = encodeBytes32String(externalId);
voucherInitValues.contractURI = `https://brand${i}.com`;

// Create a new collection
await accountHandler.connect(assistant).createNewCollection(externalId, voucherInitValues);
}

const sellersCollectionCount = await accountHandler.connect(rando).getSellersCollectionCount(seller.id);

expect(sellersCollectionCount).to.equal(expectedCollectionCount, "Incorrect number of collections");
});

it("seller does not exist", async function () {
const sellerId = 200;
const expectedCollectionCount = 0;
const sellersCollectionCount = await accountHandler.connect(rando).getSellersCollectionCount(sellerId);

expect(sellersCollectionCount).to.equal(expectedCollectionCount, "Incorrect number of collections");
});
});

context("👉 updateSellerSalt()", async function () {
let newSellerSalt;

Expand Down

0 comments on commit 25fa3cf

Please sign in to comment.