1

I'm writing unit tests for my e-commerce smart contract and they keep failing. Wonder what I'm doing wrong.

I want to check;

  1. that buyer pays exact price
  2. that seller cannot be the buyer
  3. that product is bought successfully and emits event
  • The first test passes even when the testPrice is less the required price.

  • The second test reverts with;

AssertionError: Expected transaction to be reverted with custom error 'Ecommerce__SellerCannotBeBuyer', but it reverted with custom error 'Ecommerce__PriceNotMet'

  • The third test reverts with;

Error: VM Exception while processing transaction: reverted with custom error 'Ecommerce__PriceNotMet()'

Ecommerce.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Ecommerce {
    // properties of a product
    struct Product {
        string title;
        string description;
        uint price;
        uint productId;
        address payable seller;
        address buyer;
        bool delivered;
    }

    Product[] public products;
    uint counter = 1;

    //errors
    error Ecommerce__PriceMustBeGreater0();
    error Ecommerce__PriceNotMet();
    error Ecommerce__SellerCannotBeBuyer();

    // events
    event Registered(string title, uint productId, address seller);
    event Bought(uint productId, address buyer);
    event Delivered(uint productId);

    constructor() {}

    function registerProduct(
        string memory _title,
        string memory _description,
        uint _price
    ) public {
        //require(_price > 0, "Price has to be greater than 0!");
        if (_price <= 0) {
            revert Ecommerce__PriceMustBeGreater0();
        }

        Product memory tempProduct;
        tempProduct.title = _title;
        tempProduct.description = _description;
        tempProduct.price = _price * 10 ** 18;
        tempProduct.seller = payable(msg.sender);
        tempProduct.productId = counter;
        products.push(tempProduct);
        counter++;

        //products[tempProduct.productId - 1].seller = payable(msg.sender);

        emit Registered(_title, tempProduct.productId, msg.sender);
    }

    function buy(uint _productId) public payable {
        /* require(
            products[_productId - 1].price == msg.value,
            "Please pay the exact price"
        ); */

        if (products[_productId - 1].price != msg.value) {
            revert Ecommerce__PriceNotMet();
        }

        /* require(
            products[_productId - 1].seller != msg.sender,
            "You can't buy your own products!"
        ); */

        if (products[_productId - 1].seller == msg.sender) {
            revert Ecommerce__SellerCannotBeBuyer();
        }
        products[_productId - 1].buyer = msg.sender;

        emit Bought(_productId, msg.sender);
    }

    function delivery(uint _productId) public {
        require(
            products[_productId - 1].buyer == msg.sender,
            "only the buyer can call this fuction"
        );
        products[_productId - 1].delivered = true;
        products[_productId - 1].seller.transfer(
            products[_productId - 1].price // we can charge a fee for the buyer's using our platform at this point eg. (price * 98%)
        );

        emit Delivered(_productId);
    }
}

test.js
describe("Buying a Product", () => {
              it("checks that buyer pays exact price", async () => {
                  const productId = 1
                  const testPrice = ethers.utils.parseEther("0")
                  // connect the buyer/player
                  const playerConnectedEcommerce = ecommerce.connect(player)

                  // first register product
                  await ecommerce.registerProduct(TITLE, DESCRIPTION, PRICE)

                  // buy
                  console.log("Now we buy")
                  await expect(
                      playerConnectedEcommerce.buy(productId, { value: testPrice })
                  ).to.be.revertedWithCustomError(ecommerce, "Ecommerce__PriceNotMet")

                  // Passes even with the testPrice as 0eth. It should fail.
              })

              it("checks that seller cannot be the buyer ", async () => {
                  const productId = 1
                  // first register product
                  await ecommerce.registerProduct(TITLE, DESCRIPTION, PRICE)
                  // buy
                  // should revert if the same person who registered(seller), tries to buy
                  await expect(
                      ecommerce.buy(productId, { value: PRICE })
                  ).to.be.revertedWithCustomError(ecommerce, "Ecommerce__SellerCannotBeBuyer")
              })

              it("checks that product is bought successfully and emits event", async () => {
                  const productId = 1
                  // connect the buyer/player
                  const playerConnectedEcommerce = ecommerce.connect(player)
                  // first register product
                  await ecommerce.registerProduct(TITLE, DESCRIPTION, PRICE)
                  // buy
                  console.log("Now we buy")
                  await expect(playerConnectedEcommerce.buy(productId)).to.emit("Bought")
              })
          })
deploy.js
const { network } = require("hardhat")
const { developmentChains } = require("../helper-hardhat-config")
const { verify } = require("../utils/verify")

module.exports = async ({ getNamedAccounts, deployments }) => {
    const { deploy, log } = deployments
    const { deployer } = await getNamedAccounts()
    log("-----------------------------------------------------")
    const args = []
    const ecommerce = await deploy("Ecommerce", {
        from: deployer,
        args: args,
        log: true,
        waitConfirmations: network.config.blockConfirmations || 1,
    })

    // verify contract
    if (!developmentChains.includes(network.name) && process.env.ETHERSCAN_API_KEY) {
        log("Verifying.....")
        await verify(ecommerce.address, args)
    }
    log("-----------------------------------------------------")
}

module.exports.tags = ["all", "ecommerce"]

For full code check my [Git Repo] (https://github.com/kihiuFrank/EcommerceSmartContract)

1 Answer 1

1

In your contract, when someone registers their product you multiply the price input by 1e18, however when you go to buy in your test you use the same value for both register and buy, and do not account for the offset.

In general, I recommend not scaling the price input to eth value, aka multiplying by 1e18. Doing this prevents people from paying say .5 eth, and also its generally bad practice, since other smart contracts are expecting to deal with raw values, converting between them on a smart contract level is not reccomended.

Leave the converting from wei to eth off chain!

1
  • Thanks a lot, mate.
    – kihiuFrank
    Mar 25, 2023 at 8:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.