Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
204 changes: 202 additions & 2 deletions e2e/tests/meta-tx.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ZERO_ADDRESS } from "./../../packages/core-sdk/tests/mocks";
import { BigNumberish } from "@ethersproject/bignumber";
import { parseEther } from "@ethersproject/units";
import { Wallet, BigNumber, constants } from "ethers";
import { OfferFieldsFragment } from "../../packages/core-sdk/src/subgraph";
import { mockCreateOfferArgs } from "../../packages/common/tests/mocks";
Expand All @@ -26,12 +27,19 @@ import {
createOfferWithCondition,
getCollectionMetadataUri,
createRandomWallet,
META_TX_API_ID_VOUCHER
META_TX_API_ID_VOUCHER,
createDisputeResolver,
deployerWallet,
initSellerAndBuyerSDKs
} from "./utils";
import { CoreSDK, forwarder } from "../../packages/core-sdk/src";
import EvaluationMethod from "../../contracts/protocol-contracts/scripts/domain/EvaluationMethod";
import TokenType from "../../contracts/protocol-contracts/scripts/domain/TokenType";
import { AuthTokenType, GatingType } from "../../packages/common";
import { AuthTokenType, GatingType, OfferCreator } from "../../packages/common";
import {
MSEC_PER_DAY,
MSEC_PER_SEC
} from "./../../packages/common/src/utils/timestamp";

const sellerWallet = seedWallet7; // be sure the seedWallet is not used by another test (to allow concurrent run)
const sellerAddress = sellerWallet.address;
Expand Down Expand Up @@ -724,6 +732,198 @@ describe("meta-tx", () => {
});
});

describe("#signMetaTxCommitToBuyerOffer()", () => {
test("native exchange token buyer-initiated offer", async () => {
const exchangeToken = constants.AddressZero;
// drFeeAmount must be 0 so the seller doesn't need to deposit funds before
// committing via meta-tx (meta-tx can't carry ETH value)
const drFeeAmount = "0";

// Create a dispute resolver with zero fee (required for meta-tx compatibility)
const { fundedWallet: drFundedWallet } =
await initCoreSDKWithFundedWallet(sellerWallet);
const drAddress = drFundedWallet.address.toLowerCase();
const { disputeResolver } = await createDisputeResolver(
drFundedWallet,
deployerWallet,
{
assistant: drAddress,
admin: drAddress,
treasury: drAddress,
metadataUri: "",
escalationResponsePeriodInMS:
90 * MSEC_PER_DAY - 1 * MSEC_PER_SEC,
fees: [
{
feeAmount: drFeeAmount,
tokenAddress: exchangeToken,
tokenName: "Native"
}
],
sellerAllowList: []
}
);

// Create fresh buyer/seller wallets
const {
sellerCoreSDK: sellerCoreSDKBuyer,
buyerCoreSDK: buyerCoreSDKBuyer,
sellerWallet: sellerFundedWallet
} = await initSellerAndBuyerSDKs(sellerWallet);

// Buyer creates a buyer-initiated offer.
// sellerDeposit must be 0 so the meta-tx relayer doesn't need to forward ETH value.
const buyerInitiatedOffer = await createOffer(buyerCoreSDKBuyer, {
creator: OfferCreator.Buyer,
quantityAvailable: 1,
disputeResolverId: disputeResolver.id,
exchangeToken,
sellerDeposit: "0"
});

// Buyer deposits offer.price to allow the seller to commit
const buyerDepositTx = await buyerCoreSDKBuyer.depositFunds(
buyerInitiatedOffer.buyerId,
buyerInitiatedOffer.price,
exchangeToken
);
await buyerCoreSDKBuyer.waitForGraphNodeIndexing(buyerDepositTx);

// Seller creates a seller account
await createSeller(sellerCoreSDKBuyer, sellerFundedWallet.address);
// No seller depositFunds needed: drFeeAmount = 0 and sellerDeposit = 0

const nonce = Date.now();

// Seller signs meta tx for commitToBuyerOffer
const { r, s, v, functionName, functionSignature } =
await sellerCoreSDKBuyer.signMetaTxCommitToBuyerOffer({
offerId: buyerInitiatedOffer.id,
sellerParams: {},
nonce
});

// Relayer executes meta tx on behalf of seller
const metaTx = await sellerCoreSDKBuyer.relayMetaTransaction({
functionName,
functionSignature,
nonce,
sigR: r,
sigS: s,
sigV: v
});
const metaTxReceipt = await metaTx.wait();
expect(metaTxReceipt.transactionHash).toBeTruthy();
expect(BigNumber.from(metaTxReceipt.effectiveGasPrice).gt(0)).toBe(true);
});

test("non-native exchange token buyer-initiated offer", async () => {
const exchangeToken = MOCK_ERC20_ADDRESS;
const drFeeAmount = parseEther("0.001");

// Create a dispute resolver with an ERC20 fee
const { fundedWallet: drFundedWallet } =
await initCoreSDKWithFundedWallet(sellerWallet);
const drAddress = drFundedWallet.address.toLowerCase();
const { disputeResolver } = await createDisputeResolver(
drFundedWallet,
deployerWallet,
{
assistant: drAddress,
admin: drAddress,
treasury: drAddress,
metadataUri: "",
escalationResponsePeriodInMS:
90 * MSEC_PER_DAY - 1 * MSEC_PER_SEC,
fees: [
{
feeAmount: drFeeAmount,
tokenAddress: exchangeToken,
tokenName: "ERC20"
}
],
sellerAllowList: []
}
);

// Create fresh buyer/seller wallets
const {
sellerCoreSDK: sellerCoreSDKBuyer,
buyerCoreSDK: buyerCoreSDKBuyer,
sellerWallet: sellerFundedWallet,
buyerWallet: buyerFundedWallet
} = await initSellerAndBuyerSDKs(sellerWallet);

// Mint ERC20 tokens to fresh wallets (they start with 0 ERC20 balance)
await ensureMintedAndAllowedTokens(
[buyerFundedWallet, sellerFundedWallet],
undefined,
false
);

// Buyer creates a buyer-initiated offer with ERC20 exchange token.
// sellerDeposit is 0 to simplify the test setup.
const buyerInitiatedOffer = await createOffer(buyerCoreSDKBuyer, {
creator: OfferCreator.Buyer,
quantityAvailable: 1,
disputeResolverId: disputeResolver.id,
exchangeToken,
sellerDeposit: "0"
});

// Buyer approves ERC20 and deposits offer.price
await approveErc20Token(
buyerCoreSDKBuyer,
exchangeToken,
buyerInitiatedOffer.price
);
const buyerDepositTx = await buyerCoreSDKBuyer.depositFunds(
buyerInitiatedOffer.buyerId,
buyerInitiatedOffer.price,
exchangeToken
);
await buyerCoreSDKBuyer.waitForGraphNodeIndexing(buyerDepositTx);

// Seller creates a seller account
const seller = await createSeller(
sellerCoreSDKBuyer,
sellerFundedWallet.address
);

// Seller approves ERC20 and deposits drFeeAmount into the protocol
await approveErc20Token(sellerCoreSDKBuyer, exchangeToken, drFeeAmount);
const sellerDepositTx = await sellerCoreSDKBuyer.depositFunds(
seller.id,
drFeeAmount,
exchangeToken
);
await sellerCoreSDKBuyer.waitForGraphNodeIndexing(sellerDepositTx);

const nonce = Date.now();

// Seller signs meta tx for commitToBuyerOffer
const { r, s, v, functionName, functionSignature } =
await sellerCoreSDKBuyer.signMetaTxCommitToBuyerOffer({
offerId: buyerInitiatedOffer.id,
sellerParams: {},
nonce
});

// Relayer executes meta tx on behalf of seller
const metaTx = await sellerCoreSDKBuyer.relayMetaTransaction({
functionName,
functionSignature,
nonce,
sigR: r,
sigS: s,
sigV: v
});
const metaTxReceipt = await metaTx.wait();
expect(metaTxReceipt.transactionHash).toBeTruthy();
expect(BigNumber.from(metaTxReceipt.effectiveGasPrice).gt(0)).toBe(true);
});
});

describe("#signMetaTxRedeemVoucher()", () => {
test("non-native exchange token offer", async () => {
const commitTx = await buyerCoreSDK.commitToOffer(offerToCommit.id);
Expand Down
22 changes: 20 additions & 2 deletions packages/core-sdk/src/meta-tx/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import {
UpdateSellerArgs,
OptInToSellerUpdateArgs,
envConfigs,
abis
abis,
SellerOfferArgs
} from "@bosonprotocol/common";
import { storeMetadataOnTheGraph } from "../offers/storage";
import { BigNumber, BigNumberish } from "@ethersproject/bignumber";
Expand All @@ -24,7 +25,8 @@ import {
} from "../accounts/interface";
import {
bosonExchangeCommitHandlerIface,
bosonExchangeHandlerIface
bosonExchangeHandlerIface,
encodeCommitToBuyerOffer
} from "../exchanges/interface";
import {
bosonOfferHandlerIface,
Expand Down Expand Up @@ -944,6 +946,22 @@ export async function signMetaTxCommitToConditionalOffer(
};
}

export async function signMetaTxCommitToBuyerOffer(
args: BaseMetaTxArgs & {
offerId: BigNumberish;
sellerParams: SellerOfferArgs;
}
): Promise<SignedMetaTx> {
const functionName =
"commitToBuyerOffer(uint256,(uint256,(address[],uint256[]),address))";

return signMetaTx({
...args,
functionName,
functionSignature: encodeCommitToBuyerOffer(args.offerId, args.sellerParams)
});
}

export async function signMetaTxCancelVoucher(
args: BaseMetaTxArgs & {
exchangeId: BigNumberish;
Expand Down
19 changes: 19 additions & 0 deletions packages/core-sdk/src/meta-tx/mixin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,25 @@ export class MetaTxMixin<T extends Web3LibAdapter> extends BaseCoreSDK<T> {
});
}

/**
* Encodes and signs a meta transaction for `commitToBuyerOffer` that can be relayed.
* @param args - Meta transaction args.
* @returns Signature.
*/
public async signMetaTxCommitToBuyerOffer(
args: Omit<
Parameters<typeof handler.signMetaTxCommitToBuyerOffer>[0],
"web3Lib" | "metaTxHandlerAddress" | "chainId"
>
) {
return handler.signMetaTxCommitToBuyerOffer({
web3Lib: this._web3Lib,
metaTxHandlerAddress: this._protocolDiamond,
chainId: this._chainId,
...args
});
}

/**
* Encodes and signs a meta transaction for `cancelVoucher` that can be relayed.
* @param args - Meta transaction args.
Expand Down
Loading