discourse/contracts/FeeBarrier.sol
2025-03-25 03:52:30 -04:00

207 lines
6.8 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/**
* @title FeeBarrier
* @dev Contract for implementing economic barriers for unverified users submitting perspectives
*/
contract FeeBarrier {
// State variables
uint256 public constant FEE_AMOUNT = 0.01 ether; // POL fee amount
uint256 public constant REFUND_PERIOD = 1 days; // Time period after which a fee can be refunded
address public owner;
// Structs
struct Content {
address user;
string issueId;
string content;
string fileUrl;
bool isVerified;
uint256 timestamp;
bool isFlagged;
bool feeRefunded;
}
// Mappings
mapping(uint256 => Content) public contents;
uint256 public contentCounter;
// Events
event FeePaid(address indexed user, uint256 indexed contentId, uint256 fee);
event FeeRefunded(address indexed user, uint256 indexed contentId, uint256 fee);
event ContentFlagged(uint256 indexed contentId, address flagger);
event ContentSubmitted(uint256 indexed contentId, address indexed user, string issueId);
// Constructor
constructor() {
owner = msg.sender;
contentCounter = 0;
}
// Modifiers
modifier onlyOwner() {
require(msg.sender == owner, "Only owner can call this function");
_;
}
/**
* @dev Submit content with a fee for unverified users
* @param issueId The ID of the issue this content relates to
* @param content The actual content text
* @param fileUrl Optional URL to an attached file
* @param isVerified Whether the user is verified
* @return contentId The ID of the submitted content
*/
function payFeeAndSubmit(
string memory issueId,
string memory content,
string memory fileUrl,
bool isVerified
) public payable returns (uint256) {
// Verified users don't need to pay a fee
if (!isVerified) {
require(msg.value >= FEE_AMOUNT, "Insufficient fee amount");
}
uint256 contentId = contentCounter++;
contents[contentId] = Content({
user: msg.sender,
issueId: issueId,
content: content,
fileUrl: fileUrl,
isVerified: isVerified,
timestamp: block.timestamp,
isFlagged: false,
feeRefunded: isVerified // Mark as refunded already if verified (no fee)
});
emit ContentSubmitted(contentId, msg.sender, issueId);
// Only emit fee event for unverified users
if (!isVerified) {
emit FeePaid(msg.sender, contentId, msg.value);
}
return contentId;
}
/**
* @dev Refund fee if content is not flagged after the refund period
* @param contentId The ID of the content to refund fee for
*/
function refundFee(uint256 contentId) public {
Content storage contentItem = contents[contentId];
require(contentId < contentCounter, "Content does not exist");
require(!contentItem.isVerified, "No fee to refund for verified users");
require(!contentItem.feeRefunded, "Fee already refunded");
require(!contentItem.isFlagged, "Cannot refund fee for flagged content");
require(
block.timestamp >= contentItem.timestamp + REFUND_PERIOD,
"Refund period not passed yet"
);
contentItem.feeRefunded = true;
// Transfer the fee back to the user
(bool success, ) = contentItem.user.call{value: FEE_AMOUNT}("");
require(success, "Refund failed");
emit FeeRefunded(contentItem.user, contentId, FEE_AMOUNT);
}
/**
* @dev Flag content for moderation (callable by verified users or contract owner)
* @param contentId The ID of the content to flag
*/
function flagContent(uint256 contentId) public {
require(contentId < contentCounter, "Content does not exist");
require(!contents[contentId].isFlagged, "Content already flagged");
// Only contract owner or verified users with special permissions can flag content
// (This would be implemented with a proper access control system in a real contract)
contents[contentId].isFlagged = true;
emit ContentFlagged(contentId, msg.sender);
}
/**
* @dev Batch refund for multiple contents that have passed the refund period
* @param contentIds Array of content IDs to refund
*/
function batchRefund(uint256[] memory contentIds) public {
for (uint256 i = 0; i < contentIds.length; i++) {
try this.refundFee(contentIds[i]) {
// Refund successful
} catch {
// Skip failed refunds
}
}
}
/**
* @dev Get content details
* @param contentId The ID of the content to get
*/
function getContent(uint256 contentId) public view returns (
address user,
string memory issueId,
string memory content,
string memory fileUrl,
bool isVerified,
uint256 timestamp,
bool isFlagged,
bool feeRefunded
) {
require(contentId < contentCounter, "Content does not exist");
Content storage contentItem = contents[contentId];
return (
contentItem.user,
contentItem.issueId,
contentItem.content,
contentItem.fileUrl,
contentItem.isVerified,
contentItem.timestamp,
contentItem.isFlagged,
contentItem.feeRefunded
);
}
/**
* @dev Check if content is eligible for refund
* @param contentId The ID of the content to check
*/
function isEligibleForRefund(uint256 contentId) public view returns (bool) {
if (contentId >= contentCounter) return false;
Content storage contentItem = contents[contentId];
return (
!contentItem.isVerified &&
!contentItem.feeRefunded &&
!contentItem.isFlagged &&
block.timestamp >= contentItem.timestamp + REFUND_PERIOD
);
}
/**
* @dev Withdraw any extra funds from the contract (only owner)
* @param amount The amount to withdraw
*/
function withdraw(uint256 amount) public onlyOwner {
require(amount <= address(this).balance, "Insufficient balance");
(bool success, ) = owner.call{value: amount}("");
require(success, "Withdrawal failed");
}
/**
* @dev Emergency withdraw function (only owner)
*/
function emergencyWithdraw() public onlyOwner {
(bool success, ) = owner.call{value: address(this).balance}("");
require(success, "Emergency withdrawal failed");
}
}