Skip to content

Commit

Permalink
Adds the context behaviour to MarketplaceAccessFacet.
Browse files Browse the repository at this point in the history
  • Loading branch information
jamesduncombe committed Feb 7, 2024
1 parent 7cc5ea7 commit 82963ea
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 6 deletions.
24 changes: 19 additions & 5 deletions contracts/marketplace/MarketplaceAccessFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ pragma solidity 0.8.10;
import "../lib/LibAddressSet.sol";
import "../lib/LibPaginate.sol";
import "../common/AHasMembers.sol";
import "../common/AHasForwarder.sol";
import "../common/AHasContext.sol";
import "../issuer/IssuerTopFacet.sol";
import "../interfaces/ICustomErrors.sol";
import "../interfaces/IHasActiveMembers.sol";
Expand All @@ -15,9 +17,21 @@ import "./MarketplaceAutomatonsFacet.sol";
* @title The Marketplace Smart Contract.
* @notice The Marketplace Access facet is in charge of keeping track of marketplace members.
*/
contract MarketplaceAccessFacet is AMarketplaceFacet, AHasMembers, IHasActiveMembers {
contract MarketplaceAccessFacet is AMarketplaceFacet, AHasMembers, AHasContext, IHasActiveMembers {
using LibAddressSet for LibAddressSet.Data;

/// AHasContext implementation.

// TODO: Could _isTrustedForwarder actually have it's default implementation point to
// IHasForwarder(address(this)).isTrustedForwarder(forwarder) or similar?
function _isTrustedForwarder(address forwarder) internal view override(AHasContext) returns (bool) {
return AHasForwarder(address(this)).isTrustedForwarder(forwarder);
}

function _msgSender() internal view override(AHasMembers, AHasContext) returns (address) {
return AHasContext._msgSender();
}

/// AHasMembers implementation.

function isMembersManager(address who) internal view override(AHasMembers) returns (bool) {
Expand Down Expand Up @@ -56,25 +70,25 @@ contract MarketplaceAccessFacet is AMarketplaceFacet, AHasMembers, IHasActiveMem
*/
function memberAddedToFast(address member) external {
// Verify that the given address is in fact a registered FAST contract.
if (!IssuerTopFacet(LibMarketplace.data().issuer).isFastRegistered(msg.sender)) {
if (!IssuerTopFacet(LibMarketplace.data().issuer).isFastRegistered(_msgSender())) {
revert ICustomErrors.RequiresFastContractCaller();
}
// Keep track of the member's FAST membership.
// TODO: We don't throw until we've fixed the `marketplace.fastMemberships`.
LibMarketplaceAccess.data().fastMemberships[member].add(msg.sender, true);
LibMarketplaceAccess.data().fastMemberships[member].add(_msgSender(), true);
}

/**
* @notice Callback from FAST contracts allowing the Marketplace contract to keep track of FAST memberships.
* @param member The member for which a FAST membership has been removed.
*/
function memberRemovedFromFast(address member) external {
if (!IssuerTopFacet(LibMarketplace.data().issuer).isFastRegistered(msg.sender)) {
if (!IssuerTopFacet(LibMarketplace.data().issuer).isFastRegistered(_msgSender())) {
revert ICustomErrors.RequiresFastContractCaller();
}
// Remove the tracked membership.
// TODO: We don't throw until we've fixed the `marketplace.fastMemberships`.
LibMarketplaceAccess.data().fastMemberships[member].remove(msg.sender, true);
LibMarketplaceAccess.data().fastMemberships[member].remove(_msgSender(), true);
}

/// IHasActiveMembers implementation.
Expand Down
91 changes: 90 additions & 1 deletion test/marketplace/MarketplaceAccessFacet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
Issuer,
Marketplace,
} from "../../typechain/hardhat-diamond-abi/HardhatDiamondABI.sol";
import { IForwarder } from "../../typechain";
chai.use(solidity);
chai.use(smock.matchers);

Expand All @@ -29,6 +30,7 @@ describe("MarketplaceAccessFacet", () => {
let issuer: FakeContract<Issuer>,
fast: FakeContract<Fast>,
marketplace: Marketplace,
forwarder: FakeContract<IForwarder>,
access: MarketplaceAccessFacet,
issuerMemberAccess: MarketplaceAccessFacet;

Expand All @@ -48,12 +50,19 @@ describe("MarketplaceAccessFacet", () => {
issuer.isFastRegistered.returns(false);
};

const resetIForwarderMock = () => {
forwarder.supportsInterface.reset();
forwarder.supportsInterface.whenCalledWith(/* IForwarder */ "0x25e23e64").returns(true);
forwarder.supportsInterface.returns(false);
}

before(async () => {
// Keep track of a few signers.
[deployer, issuerMember, alice, bob, rob, john] = await ethers.getSigners();
// Mock Issuer and Fast contracts.
// Mock Issuer, Fast and IForwarder contracts.
issuer = await smock.fake("Issuer");
fast = await smock.fake("Fast");
forwarder = await smock.fake("IForwarder");
});

beforeEach(async () => {
Expand All @@ -77,6 +86,30 @@ describe("MarketplaceAccessFacet", () => {

resetIssuerMock();
resetFastMock();
resetIForwarderMock();
});

describe("AHasContext implementation", () => {
describe("_isTrustedForwarder", () => {
it("returns true if the address is a trusted forwarder", async () => {
// Set the trusted forwarder.
forwarder.supportsInterface.reset();
forwarder.supportsInterface.whenCalledWith(/* IForwarder */ "0x25e23e64").returns(true);
forwarder.supportsInterface.returns(false);

await marketplace.connect(issuerMember).setTrustedForwarder(forwarder.address);

// isTrustedForwarder() should delegate to _isTrustedForwarder().
const subject = await marketplace.connect(issuerMember).isTrustedForwarder(forwarder.address);
expect(subject).to.eq(true);
});
});

describe("_msgSender", () => {
it("returns the original msg.sender", async () => {
// Call a function on the Marketplace contract that's sponsored.
});
});
});

describe("IHasMembers", () => {
Expand Down Expand Up @@ -171,6 +204,34 @@ describe("MarketplaceAccessFacet", () => {
});

it("calls back onMemberAdded");

it("is callable by a trusted forwarder", async () => {
// Set the trusted forwarder.
await marketplace.connect(issuerMember).setTrustedForwarder(forwarder.address);

// Impersonate the trusted forwarder contract.
const accessAsForwarder = await impersonateContract(access, forwarder.address);

// Build the data to call addMember() on the Marketplace contract.
// Pack the issuerMember address at the end - this is sponsored callers address.
const encodedFunctionCall = await access.interface.encodeFunctionData("addMember", [alice.address]);
const data = ethers.utils.solidityPack(
["bytes", "address"],
[encodedFunctionCall, issuerMember.address]
);

// As the forwarder send the packed transaction.
await accessAsForwarder.signer.sendTransaction(
{
data: data,
to: access.address,
}
);

// Check that Alice is a member.
const subject = await marketplace.isMember(alice.address);
expect(subject).to.eq(true);
});
});

describe("removeMember", () => {
Expand Down Expand Up @@ -224,6 +285,34 @@ describe("MarketplaceAccessFacet", () => {
});

it("calls back onMemberRemoved");

it("is callable by a trusted forwarder", async () => {
// Set the trusted forwarder.
await marketplace.connect(issuerMember).setTrustedForwarder(forwarder.address);

// Impersonate the trusted forwarder contract.
const accessAsForwarder = await impersonateContract(access, forwarder.address);

// Build the data to call removeMember() on the Marketplace contract.
// Pack the issuerMember address at the end - this is sponsored callers address.
const encodedFunctionCall = await access.interface.encodeFunctionData("removeMember", [alice.address]);
const data = ethers.utils.solidityPack(
["bytes", "address"],
[encodedFunctionCall, issuerMember.address]
);

// As the forwarder send the packed transaction.
await accessAsForwarder.signer.sendTransaction(
{
data: data,
to: access.address,
}
);

// Check that Alice is not member.
const subject = await marketplace.isMember(alice.address);
expect(subject).to.eq(false);
});
});

describe("onMemberAdded", () => {
Expand Down

0 comments on commit 82963ea

Please sign in to comment.