This commit is contained in:
2026-04-05 00:43:23 +05:30
commit 8be37d3e92
425 changed files with 101853 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MockERC20 is ERC20 {
constructor(string memory name, string memory symbol) ERC20(name, symbol) {}
function mint(address to, uint256 amount) external {
_mint(to, amount);
}
}

View File

@@ -0,0 +1,288 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/Pausable.sol";
/**
* @title TaskEscrow
* @dev Escrow smart contract for AI-powered micro-task marketplace on CELO Sepolia Testnet.
* @notice This contract securely holds cUSD payments for tasks, releasing to workers upon AI-verification approval.
*/
contract TaskEscrow is Ownable, ReentrancyGuard, Pausable {
using SafeERC20 for IERC20;
// ============ Constants & Parameters ============
IERC20 public immutable cUSD;
uint256 public constant PLATFORM_FEE_BPS = 500; // 5%
uint256 public constant BPS_DENOMINATOR = 10000;
uint256 public platformFeesAccumulated;
uint256 public taskCounter;
// ============ Enums ============
enum TaskStatus {
Open,
InProgress,
Completed,
Cancelled,
Expired
}
// ============ Struct ============
struct Task {
uint256 taskId;
address requester;
address worker;
uint256 paymentAmount;
TaskStatus status;
uint256 createdAt;
uint256 expiresAt;
}
// ============ Mappings ============
mapping(uint256 => Task) public tasks;
mapping(uint256 => bool) public taskExists;
// ============ Events ============
event TaskCreated(
uint256 indexed taskId,
address indexed requester,
uint256 payment,
uint256 expiresAt
);
event WorkerAssigned(uint256 indexed taskId, address indexed worker);
event PaymentReleased(
uint256 indexed taskId,
address indexed worker,
uint256 workerAmount,
uint256 platformFee
);
event TaskCancelled(
uint256 indexed taskId,
address indexed requester,
uint256 refunded
);
event TaskExpired(
uint256 indexed taskId,
address indexed requester,
uint256 refunded
);
event PlatformFeesWithdrawn(address indexed owner, uint256 amount);
event DebugLog(
string action,
address sender,
uint256 taskId,
uint256 timestamp
);
// ============ Modifiers ============
modifier validTask(uint256 _taskId) {
require(taskExists[_taskId], "Invalid task ID");
_;
}
modifier onlyRequester(uint256 _taskId) {
require(tasks[_taskId].requester == msg.sender, "Not requester");
_;
}
modifier taskIsOpen(uint256 _taskId) {
require(tasks[_taskId].status == TaskStatus.Open, "Not open");
_;
}
modifier taskInProgress(uint256 _taskId) {
require(
tasks[_taskId].status == TaskStatus.InProgress,
"Not in progress"
);
_;
}
// ============ Constructor ============
/**
* @param _cUSDAddress Valid cUSD contract address on Celo Sepolia.
*/
constructor(address _cUSDAddress) Ownable(msg.sender) {
require(_cUSDAddress != address(0), "Invalid cUSD address");
cUSD = IERC20(_cUSDAddress);
}
// ============ Core Logic ============
function createTask(
uint256 _paymentAmount,
uint256 _durationInDays
) external whenNotPaused nonReentrant returns (uint256) {
require(_paymentAmount > 0, "Invalid amount");
require(
_durationInDays > 0 && _durationInDays <= 90,
"Invalid duration"
);
taskCounter++;
uint256 newTaskId = taskCounter;
uint256 expiry = block.timestamp + (_durationInDays * 1 days);
cUSD.safeTransferFrom(msg.sender, address(this), _paymentAmount);
tasks[newTaskId] = Task({
taskId: newTaskId,
requester: msg.sender,
worker: address(0),
paymentAmount: _paymentAmount,
status: TaskStatus.Open,
createdAt: block.timestamp,
expiresAt: expiry
});
taskExists[newTaskId] = true;
emit TaskCreated(newTaskId, msg.sender, _paymentAmount, expiry);
emit DebugLog("createTask", msg.sender, newTaskId, block.timestamp);
return newTaskId;
}
function assignWorker(
uint256 _taskId,
address _worker
) external validTask(_taskId) taskIsOpen(_taskId) whenNotPaused {
Task storage task = tasks[_taskId];
require(
msg.sender == task.requester || msg.sender == owner(),
"Not authorized"
);
require(_worker != address(0), "Invalid worker");
require(_worker != task.requester, "Requester cannot be worker");
task.worker = _worker;
task.status = TaskStatus.InProgress;
emit WorkerAssigned(_taskId, _worker);
emit DebugLog("assignWorker", msg.sender, _taskId, block.timestamp);
}
function approveSubmission(
uint256 _taskId
)
external
onlyOwner
validTask(_taskId)
taskInProgress(_taskId)
nonReentrant
{
Task storage task = tasks[_taskId];
uint256 total = task.paymentAmount;
uint256 platformFee = (total * PLATFORM_FEE_BPS) / BPS_DENOMINATOR;
uint256 workerShare = total - platformFee;
task.status = TaskStatus.Completed;
platformFeesAccumulated += platformFee;
cUSD.safeTransfer(task.worker, workerShare);
emit PaymentReleased(_taskId, task.worker, workerShare, platformFee);
emit DebugLog(
"approveSubmission",
msg.sender,
_taskId,
block.timestamp
);
}
function rejectSubmission(
uint256 _taskId
)
external
onlyOwner
validTask(_taskId)
taskInProgress(_taskId)
nonReentrant
{
Task storage task = tasks[_taskId];
task.status = TaskStatus.Cancelled;
cUSD.safeTransfer(task.requester, task.paymentAmount);
emit TaskCancelled(_taskId, task.requester, task.paymentAmount);
emit DebugLog("rejectSubmission", msg.sender, _taskId, block.timestamp);
}
function cancelTask(
uint256 _taskId
)
external
validTask(_taskId)
taskIsOpen(_taskId)
onlyRequester(_taskId)
nonReentrant
{
Task storage task = tasks[_taskId];
task.status = TaskStatus.Cancelled;
cUSD.safeTransfer(task.requester, task.paymentAmount);
emit TaskCancelled(_taskId, task.requester, task.paymentAmount);
emit DebugLog("cancelTask", msg.sender, _taskId, block.timestamp);
}
function claimExpiredTask(
uint256 _taskId
) external validTask(_taskId) nonReentrant {
Task storage task = tasks[_taskId];
require(block.timestamp >= task.expiresAt, "Not expired");
require(
task.status == TaskStatus.Open ||
task.status == TaskStatus.InProgress,
"Already finalized"
);
require(msg.sender == task.requester, "Only requester");
task.status = TaskStatus.Expired;
cUSD.safeTransfer(task.requester, task.paymentAmount);
emit TaskExpired(_taskId, task.requester, task.paymentAmount);
emit DebugLog("claimExpiredTask", msg.sender, _taskId, block.timestamp);
}
function withdrawPlatformFees() external onlyOwner nonReentrant {
uint256 amount = platformFeesAccumulated;
require(amount > 0, "No fees");
platformFeesAccumulated = 0;
cUSD.safeTransfer(owner(), amount);
emit PlatformFeesWithdrawn(owner(), amount);
emit DebugLog("withdrawPlatformFees", msg.sender, 0, block.timestamp);
}
// ============ Views ============
function getTask(
uint256 _taskId
) external view validTask(_taskId) returns (Task memory) {
return tasks[_taskId];
}
function isTaskExpired(
uint256 _taskId
) external view validTask(_taskId) returns (bool) {
return block.timestamp >= tasks[_taskId].expiresAt;
}
function getContractBalance() external view returns (uint256) {
return cUSD.balanceOf(address(this));
}
// ============ Admin Controls ============
function pause() external onlyOwner {
_pause();
}
function unpause() external onlyOwner {
_unpause();
}
}