I am having issues with a specific functionality on this smart contract. The contract allows for the purchasing of courses to be done without issue. However, when the admin account attempts to activate/deactivate the purchased courses, then MetaMask opens with the following error:
This transaction is expected to fail. Trying to execute it is expected to be expensive but fail, and is not recommended.
Also the gas fees are crazy high as shown here:
Total
0.12771752ETH
Amount + gas fee
Max amount:
0.12771752ETH
I have ran truffle test on both of these functions and they pass without issue. Please help me understand what is going on and how to remedy the problem. The entire contract is shown below:
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;
contract CourseMarketplace {
enum State {
Purchased,
Activated,
Deactivated
}
struct Course {
uint id;
uint price;
bytes32 proof;
address owner;
State state;
}
mapping(bytes32 => Course) private ownedCourses;
mapping(uint => bytes32) private ownedCourseHash;
uint private totalOwnedCourses;
address payable private owner;
constructor(){
setContractOwner(msg.sender);
}
/// Course has already been purchased!
error CourseHasOwner();
/// Sender is not course owner!
error SenderIsNotCourseOwner();
/// Only owner has an access!
error OnlyOwner();
/// Course has invalid state!
error InvalidState();
/// Course is not created!
error CourseIsNotCreated();
modifier onlyOwner() {
if(msg.sender != getContractOwner()){
revert OnlyOwner();
}
_;
}
function purchaseCourse(bytes16 courseId, bytes32 proof) external payable {
bytes32 courseHash = keccak256(abi.encodePacked(courseId, msg.sender));
if(hasCourseOwnership(courseHash)){
revert CourseHasOwner();
}
uint id = totalOwnedCourses++;
ownedCourseHash[id] = courseHash;
ownedCourses[courseHash] = Course({
id: id,
price: msg.value,
proof: proof,
owner: msg.sender,
state: State.Purchased
});
}
function repurchaseCourse(bytes32 courseHash) external payable {
if (!isCourseCreated(courseHash)){
revert CourseIsNotCreated();
}
if (!hasCourseOwnership(courseHash)){
revert SenderIsNotCourseOwner();
}
Course storage course = ownedCourses[courseHash];
if (course.state != State.Deactivated){
revert InvalidState();
}
course.state = State.Purchased;
course.price = msg.value;
}
function activateCourse(bytes32 courseHash) external onlyOwner {
if (!isCourseCreated(courseHash)){
revert CourseIsNotCreated();
}
Course storage course = ownedCourses[courseHash];
if(course.state != State.Purchased){
revert InvalidState();
}
course.state = State.Activated;
}
function deactivateCourse(bytes32 courseHash) external onlyOwner {
if (!isCourseCreated(courseHash)){
revert CourseIsNotCreated();
}
Course storage course = ownedCourses[courseHash];
if(course.state != State.Purchased){
revert InvalidState();
}
//Transfers ether back to owner
(bool success, ) = course.owner.call{value: course.price}("");
require(success, "Transfer failed!");
course.state = State.Deactivated;
course.price = 0;
}
function transferOwnership(address newOwner) external onlyOwner {
setContractOwner(newOwner);
}
function getCourseCount() external view returns(uint){
return totalOwnedCourses;
}
function getCourseHashAtIndex(uint index) external view returns(bytes32){
return ownedCourseHash[index];
}
function getCourseByHash(bytes32 courseHash) external view returns(Course memory){
return ownedCourses[courseHash];
}
function getContractOwner() public view returns(address) {
return owner;
}
function setContractOwner(address newOwner) private {
owner = payable(newOwner);
}
// this is a helper function for activateCourse function
function isCourseCreated(bytes32 courseHash) private view returns (bool) {
return ownedCourses[courseHash].owner != 0x0000000000000000000000000000000000000000;
}
function hasCourseOwnership(bytes32 courseHash) private view returns(bool) {
return ownedCourses[courseHash].owner == msg.sender;
}
}
Also this is how I am calling it in the front end - I am using NextJS:
const changeCourseState = async (courseHash, method) => {
try {
//[method] = "activateCourse" or "deactivateCourse"
await contract.methods[method](courseHash).send({
from: account.data
})
} catch (error) {
console.error(error.message);
}
}
const activateCourse = async (courseHash) => {
changeCourseState(courseHash, "activateCourse")
}
const deactivateCourse = async (courseHash) => {
changeCourseState(courseHash, "deactivateCourse")
}
Many thanks for any and all help.
-Bodi