Files
aitbc/contracts/EscrowService.sol
oib 7bb2905cca Update database paths and fix foreign key references across coordinator API
- Change SQLite database path from `/home/oib/windsurf/aitbc/data/` to `/opt/data/`
- Fix foreign key references to use correct table names (users, wallets, gpu_registry)
- Replace governance router with new governance and community routers
- Add multi-modal RL router to main application
- Simplify DEPLOYMENT_READINESS_REPORT.md to focus on production deployment status
- Update governance router with decentralized DAO voting
2026-02-26 19:32:06 +01:00

881 lines
27 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./AIPowerRental.sol";
import "./AITBCPaymentProcessor.sol";
/**
* @title Escrow Service
* @dev Advanced escrow service with multi-signature, time-locks, and conditional releases
* @notice Secure payment holding with automated release conditions and dispute protection
*/
contract EscrowService is Ownable, ReentrancyGuard, Pausable {
// State variables
IERC20 public aitbcToken;
AIPowerRental public aiPowerRental;
AITBCPaymentProcessor public paymentProcessor;
uint256 public escrowCounter;
uint256 public minEscrowAmount = 1e15; // 0.001 AITBC minimum
uint256 public maxEscrowAmount = 1e22; // 10,000 AITBC maximum
uint256 public minTimeLock = 300; // 5 minutes minimum
uint256 public maxTimeLock = 86400 * 30; // 30 days maximum
uint256 public defaultReleaseDelay = 3600; // 1 hour default
uint256 public emergencyReleaseDelay = 86400; // 24 hours for emergency
uint256 public platformFeePercentage = 50; // 0.5% in basis points
// Structs
struct EscrowAccount {
uint256 escrowId;
address depositor;
address beneficiary;
address arbiter;
uint256 amount;
uint256 platformFee;
uint256 releaseTime;
uint256 creationTime;
bool isReleased;
bool isRefunded;
bool isFrozen;
EscrowType escrowType;
ReleaseCondition releaseCondition;
bytes32 conditionHash;
string conditionDescription;
uint256 releaseAttempts;
uint256 lastReleaseAttempt;
address[] signatories;
mapping(address => bool) hasSigned;
uint256 requiredSignatures;
uint256 currentSignatures;
}
struct ConditionalRelease {
uint256 escrowId;
bytes32 condition;
bool conditionMet;
address oracle;
uint256 verificationTime;
string conditionData;
uint256 confidence;
}
struct MultiSigRelease {
uint256 escrowId;
address[] requiredSigners;
uint256 signaturesRequired;
mapping(address => bool) hasSigned;
uint256 currentSignatures;
uint256 deadline;
bool isExecuted;
}
struct TimeLockRelease {
uint256 escrowId;
uint256 lockStartTime;
uint256 lockDuration;
uint256 releaseWindow;
bool canEarlyRelease;
uint256 earlyReleaseFee;
bool isReleased;
}
struct EmergencyRelease {
uint256 escrowId;
address initiator;
string reason;
uint256 requestTime;
uint256 votingDeadline;
mapping(address => bool) hasVoted;
uint256 votesFor;
uint256 votesAgainst;
uint256 totalVotes;
bool isApproved;
bool isExecuted;
}
// Enums
enum EscrowType {
Standard,
MultiSignature,
TimeLocked,
Conditional,
PerformanceBased,
MilestoneBased,
Emergency
}
enum ReleaseCondition {
Manual,
Automatic,
OracleVerified,
PerformanceMet,
TimeBased,
MultiSignature,
Emergency
}
enum EscrowStatus {
Created,
Funded,
Locked,
ConditionPending,
Approved,
Released,
Refunded,
Disputed,
Frozen,
Expired
}
// Mappings
mapping(uint256 => EscrowAccount) public escrowAccounts;
mapping(uint256 => ConditionalRelease) public conditionalReleases;
mapping(uint256 => MultiSigRelease) public multiSigReleases;
mapping(uint256 => TimeLockRelease) public timeLockReleases;
mapping(uint256 => EmergencyRelease) public emergencyReleases;
mapping(address => uint256[]) public depositorEscrows;
mapping(address => uint256[]) public beneficiaryEscrows;
mapping(bytes32 => uint256) public conditionEscrows;
mapping(address => bool) public authorizedOracles;
mapping(address => bool) public authorizedArbiters;
// Arrays for tracking
uint256[] public activeEscrows;
uint256[] public pendingReleases;
// Events
event EscrowCreated(
uint256 indexed escrowId,
address indexed depositor,
address indexed beneficiary,
uint256 amount,
EscrowType escrowType,
ReleaseCondition releaseCondition
);
event EscrowFunded(
uint256 indexed escrowId,
uint256 amount,
uint256 platformFee
);
event EscrowReleased(
uint256 indexed escrowId,
address indexed beneficiary,
uint256 amount,
string reason
);
event EscrowRefunded(
uint256 indexed escrowId,
address indexed depositor,
uint256 amount,
string reason
);
event ConditionSet(
uint256 indexed escrowId,
bytes32 indexed condition,
address indexed oracle,
string conditionDescription
);
event ConditionMet(
uint256 indexed escrowId,
bytes32 indexed condition,
bool conditionMet,
uint256 verificationTime
);
event MultiSignatureRequired(
uint256 indexed escrowId,
address[] requiredSigners,
uint256 signaturesRequired
);
event SignatureSubmitted(
uint256 indexed escrowId,
address indexed signer,
uint256 currentSignatures,
uint256 requiredSignatures
);
event TimeLockSet(
uint256 indexed escrowId,
uint256 lockDuration,
uint256 releaseWindow,
bool canEarlyRelease
);
event EmergencyReleaseRequested(
uint256 indexed escrowId,
address indexed initiator,
string reason,
uint256 votingDeadline
);
event EmergencyReleaseApproved(
uint256 indexed escrowId,
uint256 votesFor,
uint256 votesAgainst,
bool approved
);
event EscrowFrozen(
uint256 indexed escrowId,
address indexed freezer,
string reason
);
event EscrowUnfrozen(
uint256 indexed escrowId,
address indexed unfreezer,
string reason
);
event PlatformFeeCollected(
uint256 indexed escrowId,
uint256 feeAmount,
address indexed collector
);
// Modifiers
modifier onlyAuthorizedOracle() {
require(authorizedOracles[msg.sender], "Not authorized oracle");
_;
}
modifier onlyAuthorizedArbiter() {
require(authorizedArbiters[msg.sender], "Not authorized arbiter");
_;
}
modifier escrowExists(uint256 _escrowId) {
require(_escrowId < escrowCounter, "Escrow does not exist");
_;
}
modifier onlyParticipant(uint256 _escrowId) {
require(
msg.sender == escrowAccounts[_escrowId].depositor ||
msg.sender == escrowAccounts[_escrowId].beneficiary ||
msg.sender == escrowAccounts[_escrowId].arbiter,
"Not escrow participant"
);
_;
}
modifier sufficientBalance(address _user, uint256 _amount) {
require(aitbcToken.balanceOf(_user) >= _amount, "Insufficient balance");
_;
}
modifier sufficientAllowance(address _user, uint256 _amount) {
require(aitbcToken.allowance(_user, address(this)) >= _amount, "Insufficient allowance");
_;
}
modifier escrowNotFrozen(uint256 _escrowId) {
require(!escrowAccounts[_escrowId].isFrozen, "Escrow is frozen");
_;
}
modifier escrowNotReleased(uint256 _escrowId) {
require(!escrowAccounts[_escrowId].isReleased, "Escrow already released");
_;
}
modifier escrowNotRefunded(uint256 _escrowId) {
require(!escrowAccounts[_escrowId].isRefunded, "Escrow already refunded");
_;
}
// Constructor
constructor(
address _aitbcToken,
address _aiPowerRental,
address _paymentProcessor
) {
aitbcToken = IERC20(_aitbcToken);
aiPowerRental = AIPowerRental(_aiPowerRental);
paymentProcessor = AITBCPaymentProcessor(_paymentProcessor);
escrowCounter = 0;
}
/**
* @dev Creates a new escrow account
* @param _beneficiary Beneficiary address
* @param _arbiter Arbiter address (can be zero address for no arbiter)
* @param _amount Amount to lock in escrow
* @param _escrowType Type of escrow
* @param _releaseCondition Release condition
* @param _releaseTime Release time (0 for no time limit)
* @param _conditionDescription Description of release conditions
*/
function createEscrow(
address _beneficiary,
address _arbiter,
uint256 _amount,
EscrowType _escrowType,
ReleaseCondition _releaseCondition,
uint256 _releaseTime,
string memory _conditionDescription
) external sufficientBalance(msg.sender, _amount) sufficientAllowance(msg.sender, _amount) nonReentrant whenNotPaused returns (uint256) {
require(_beneficiary != address(0), "Invalid beneficiary");
require(_beneficiary != msg.sender, "Cannot be own beneficiary");
require(_amount >= minEscrowAmount && _amount <= maxEscrowAmount, "Invalid amount");
require(_releaseTime == 0 || _releaseTime > block.timestamp, "Invalid release time");
uint256 escrowId = escrowCounter++;
uint256 platformFee = (_amount * platformFeePercentage) / 10000;
uint256 totalAmount = _amount + platformFee;
escrowAccounts[escrowId] = EscrowAccount({
escrowId: escrowId,
depositor: msg.sender,
beneficiary: _beneficiary,
arbiter: _arbiter,
amount: _amount,
platformFee: platformFee,
releaseTime: _releaseTime,
creationTime: block.timestamp,
isReleased: false,
isRefunded: false,
isFrozen: false,
escrowType: _escrowType,
releaseCondition: _releaseCondition,
conditionHash: bytes32(0),
conditionDescription: _conditionDescription,
releaseAttempts: 0,
lastReleaseAttempt: 0,
signatories: new address[](0),
requiredSignatures: 0,
currentSignatures: 0
});
depositorEscrows[msg.sender].push(escrowId);
beneficiaryEscrows[_beneficiary].push(escrowId);
activeEscrows.push(escrowId);
// Transfer tokens to contract
require(
aitbcToken.transferFrom(msg.sender, address(this), totalAmount),
"Escrow funding failed"
);
emit EscrowCreated(escrowId, msg.sender, _beneficiary, _amount, _escrowType, _releaseCondition);
emit EscrowFunded(escrowId, _amount, platformFee);
// Setup specific escrow type configurations
if (_escrowType == EscrowType.TimeLocked) {
_setupTimeLock(escrowId, _releaseTime - block.timestamp);
} else if (_escrowType == EscrowType.MultiSignature) {
_setupMultiSignature(escrowId);
}
return escrowId;
}
/**
* @dev Sets release condition for escrow
* @param _escrowId ID of the escrow
* @param _condition Condition hash
* @param _oracle Oracle address for verification
* @param _conditionData Condition data
*/
function setReleaseCondition(
uint256 _escrowId,
bytes32 _condition,
address _oracle,
string memory _conditionData
) external escrowExists(_escrowId) onlyParticipant(_escrowId) escrowNotFrozen(_escrowId) escrowNotReleased(_escrowId) {
require(authorizedOracles[_oracle] || _oracle == address(0), "Invalid oracle");
EscrowAccount storage escrow = escrowAccounts[_escrowId];
escrow.conditionHash = _condition;
conditionalReleases[_escrowId] = ConditionalRelease({
escrowId: _escrowId,
condition: _condition,
conditionMet: false,
oracle: _oracle,
verificationTime: 0,
conditionData: _conditionData,
confidence: 0
});
conditionEscrows[_condition] = _escrowId;
emit ConditionSet(_escrowId, _condition, _oracle, _conditionData);
}
/**
* @dev Verifies and meets release condition
* @param _escrowId ID of the escrow
* @param _conditionMet Whether condition is met
* @param _confidence Confidence level (0-100)
*/
function verifyCondition(
uint256 _escrowId,
bool _conditionMet,
uint256 _confidence
) external onlyAuthorizedOracle escrowExists(_escrowId) escrowNotFrozen(_escrowId) escrowNotReleased(_escrowId) {
ConditionalRelease storage condRelease = conditionalReleases[_escrowId];
require(condRelease.oracle == msg.sender, "Not assigned oracle");
condRelease.conditionMet = _conditionMet;
condRelease.verificationTime = block.timestamp;
condRelease.confidence = _confidence;
emit ConditionMet(_escrowId, condRelease.condition, _conditionMet, block.timestamp);
if (_conditionMet) {
_releaseEscrow(_escrowId, "Condition verified and met");
}
}
/**
* @dev Submits signature for multi-signature release
* @param _escrowId ID of the escrow
*/
function submitSignature(uint256 _escrowId)
external
escrowExists(_escrowId)
onlyParticipant(_escrowId)
escrowNotFrozen(_escrowId)
escrowNotReleased(_escrowId)
{
EscrowAccount storage escrow = escrowAccounts[_escrowId];
MultiSigRelease storage multiSig = multiSigReleases[_escrowId];
require(!escrow.hasSigned[msg.sender], "Already signed");
require(multiSig.requiredSigners > 0, "Multi-signature not setup");
escrow.hasSigned[msg.sender] = true;
escrow.currentSignatures++;
multiSig.currentSignatures++;
emit SignatureSubmitted(_escrowId, msg.sender, escrow.currentSignatures, multiSig.requiredSigners);
if (escrow.currentSignatures >= multiSig.requiredSigners) {
_releaseEscrow(_escrowId, "Multi-signature requirement met");
}
}
/**
* @dev Releases escrow to beneficiary
* @param _escrowId ID of the escrow
* @param _reason Reason for release
*/
function releaseEscrow(uint256 _escrowId, string memory _reason)
external
escrowExists(_escrowId)
escrowNotFrozen(_escrowId)
escrowNotReleased(_escrowId)
escrowNotRefunded(_escrowId)
nonReentrant
{
EscrowAccount storage escrow = escrowAccounts[_escrowId];
require(
msg.sender == escrow.depositor ||
msg.sender == escrow.beneficiary ||
msg.sender == escrow.arbiter ||
msg.sender == owner(),
"Not authorized to release"
);
// Check release conditions
if (escrow.releaseCondition == ReleaseCondition.Manual) {
require(msg.sender == escrow.depositor || msg.sender == escrow.arbiter, "Manual release requires depositor or arbiter");
} else if (escrow.releaseCondition == ReleaseCondition.TimeBased) {
require(block.timestamp >= escrow.releaseTime, "Release time not reached");
} else if (escrow.releaseCondition == ReleaseCondition.OracleVerified) {
require(conditionalReleases[_escrowId].conditionMet, "Condition not met");
} else if (escrow.releaseCondition == ReleaseCondition.MultiSignature) {
require(escrow.currentSignatures >= escrow.requiredSignatures, "Insufficient signatures");
}
_releaseEscrow(_escrowId, _reason);
}
/**
* @dev Refunds escrow to depositor
* @param _escrowId ID of the escrow
* @param _reason Reason for refund
*/
function refundEscrow(uint256 _escrowId, string memory _reason)
external
escrowExists(_escrowId)
escrowNotFrozen(_escrowId)
escrowNotReleased(_escrowId)
escrowNotRefunded(_escrowId)
nonReentrant
{
EscrowAccount storage escrow = escrowAccounts[_escrowId];
require(
msg.sender == escrow.depositor ||
msg.sender == escrow.arbiter ||
msg.sender == owner(),
"Not authorized to refund"
);
// Check refund conditions
if (escrow.releaseCondition == ReleaseCondition.TimeBased) {
require(block.timestamp < escrow.releaseTime, "Release time passed, cannot refund");
} else if (escrow.releaseCondition == ReleaseCondition.OracleVerified) {
require(!conditionalReleases[_escrowId].conditionMet, "Condition met, cannot refund");
}
escrow.isRefunded = true;
require(
aitbcToken.transfer(escrow.depositor, escrow.amount),
"Refund transfer failed"
);
emit EscrowRefunded(_escrowId, escrow.depositor, escrow.amount, _reason);
}
/**
* @dev Requests emergency release
* @param _escrowId ID of the escrow
* @param _reason Reason for emergency release
*/
function requestEmergencyRelease(uint256 _escrowId, string memory _reason)
external
escrowExists(_escrowId)
onlyParticipant(_escrowId)
escrowNotFrozen(_escrowId)
escrowNotReleased(_escrowId)
escrowNotRefunded(_escrowId)
{
EscrowAccount storage escrow = escrowAccounts[_escrowId];
EmergencyRelease storage emergency = emergencyReleases[_escrowId];
require(emergency.requestTime == 0, "Emergency release already requested");
emergencyReleases[_escrowId] = EmergencyRelease({
escrowId: _escrowId,
initiator: msg.sender,
reason: _reason,
requestTime: block.timestamp,
votingDeadline: block.timestamp + emergencyReleaseDelay,
votesFor: 0,
votesAgainst: 0,
totalVotes: 0,
isApproved: false,
isExecuted: false
});
emit EmergencyReleaseRequested(_escrowId, msg.sender, _reason, block.timestamp + emergencyReleaseDelay);
}
/**
* @dev Votes on emergency release
* @param _escrowId ID of the escrow
* @param _vote True to approve, false to reject
*/
function voteEmergencyRelease(uint256 _escrowId, bool _vote)
external
escrowExists(_escrowId)
onlyAuthorizedArbiter
{
EmergencyRelease storage emergency = emergencyReleases[_escrowId];
require(emergency.requestTime > 0, "No emergency release requested");
require(block.timestamp <= emergency.votingDeadline, "Voting deadline passed");
require(!emergency.hasVoted[msg.sender], "Already voted");
emergency.hasVoted[msg.sender] = true;
emergency.totalVotes++;
if (_vote) {
emergency.votesFor++;
} else {
emergency.votesAgainst++;
}
// Check if voting is complete and approved
if (emergency.totalVotes >= 3 && emergency.votesFor > emergency.votesAgainst) {
emergency.isApproved = true;
emit EmergencyReleaseApproved(_escrowId, emergency.votesFor, emergency.votesAgainst, true);
_releaseEscrow(_escrowId, "Emergency release approved");
}
}
/**
* @dev Freezes an escrow account
* @param _escrowId ID of the escrow
* @param _reason Reason for freezing
*/
function freezeEscrow(uint256 _escrowId, string memory _reason)
external
escrowExists(_escrowId)
escrowNotFrozen(_escrowId)
{
require(
msg.sender == escrowAccounts[_escrowId].arbiter ||
msg.sender == owner(),
"Not authorized to freeze"
);
escrowAccounts[_escrowId].isFrozen = true;
emit EscrowFrozen(_escrowId, msg.sender, _reason);
}
/**
* @dev Unfreezes an escrow account
* @param _escrowId ID of the escrow
* @param _reason Reason for unfreezing
*/
function unfreezeEscrow(uint256 _escrowId, string memory _reason)
external
escrowExists(_escrowId)
{
require(
msg.sender == escrowAccounts[_escrowId].arbiter ||
msg.sender == owner(),
"Not authorized to unfreeze"
);
escrowAccounts[_escrowId].isFrozen = false;
emit EscrowUnfrozen(_escrowId, msg.sender, _reason);
}
/**
* @dev Authorizes an oracle
* @param _oracle Address of the oracle
*/
function authorizeOracle(address _oracle) external onlyOwner {
require(_oracle != address(0), "Invalid oracle address");
authorizedOracles[_oracle] = true;
}
/**
* @dev Revokes oracle authorization
* @param _oracle Address of the oracle
*/
function revokeOracle(address _oracle) external onlyOwner {
authorizedOracles[_oracle] = false;
}
/**
* @dev Authorizes an arbiter
* @param _arbiter Address of the arbiter
*/
function authorizeArbiter(address _arbiter) external onlyOwner {
require(_arbiter != address(0), "Invalid arbiter address");
authorizedArbiters[_arbiter] = true;
}
/**
* @dev Revokes arbiter authorization
* @param _arbiter Address of the arbiter
*/
function revokeArbiter(address _arbiter) external onlyOwner {
authorizedArbiters[_arbiter] = false;
}
// Internal functions
function _setupTimeLock(uint256 _escrowId, uint256 _duration) internal {
require(_duration >= minTimeLock && _duration <= maxTimeLock, "Invalid duration");
timeLockReleases[_escrowId] = TimeLockRelease({
escrowId: _escrowId,
lockStartTime: block.timestamp,
lockDuration: _duration,
releaseWindow: _duration / 10, // 10% of lock duration as release window
canEarlyRelease: false,
earlyReleaseFee: 1000, // 10% fee for early release
isReleased: false
});
emit TimeLockSet(_escrowId, _duration, _duration / 10, false);
}
function _setupMultiSignature(uint256 _escrowId) internal {
EscrowAccount storage escrow = escrowAccounts[_escrowId];
// Default to requiring depositor + beneficiary signatures
address[] memory requiredSigners = new address[](2);
requiredSigners[0] = escrow.depositor;
requiredSigners[1] = escrow.beneficiary;
multiSigReleases[_escrowId] = MultiSigRelease({
escrowId: _escrowId,
requiredSigners: requiredSigners,
signaturesRequired: 2,
currentSignatures: 0,
deadline: block.timestamp + 7 days,
isExecuted: false
});
escrow.requiredSignatures = 2;
emit MultiSignatureRequired(_escrowId, requiredSigners, 2);
}
function _releaseEscrow(uint256 _escrowId, string memory _reason) internal {
EscrowAccount storage escrow = escrowAccounts[_escrowId];
escrow.isReleased = true;
escrow.lastReleaseAttempt = block.timestamp;
// Transfer amount to beneficiary
require(
aitbcToken.transfer(escrow.beneficiary, escrow.amount),
"Escrow release failed"
);
// Transfer platform fee to owner
if (escrow.platformFee > 0) {
require(
aitbcToken.transfer(owner(), escrow.platformFee),
"Platform fee transfer failed"
);
emit PlatformFeeCollected(_escrowId, escrow.platformFee, owner());
}
emit EscrowReleased(_escrowId, escrow.beneficiary, escrow.amount, _reason);
}
// View functions
/**
* @dev Gets escrow account details
* @param _escrowId ID of the escrow
*/
function getEscrowAccount(uint256 _escrowId)
external
view
escrowExists(_escrowId)
returns (EscrowAccount memory)
{
return escrowAccounts[_escrowId];
}
/**
* @dev Gets conditional release details
* @param _escrowId ID of the escrow
*/
function getConditionalRelease(uint256 _escrowId)
external
view
returns (ConditionalRelease memory)
{
return conditionalReleases[_escrowId];
}
/**
* @dev Gets multi-signature release details
* @param _escrowId ID of the escrow
*/
function getMultiSigRelease(uint256 _escrowId)
external
view
returns (MultiSigRelease memory)
{
return multiSigReleases[_escrowId];
}
/**
* @dev Gets time-lock release details
* @param _escrowId ID of the escrow
*/
function getTimeLockRelease(uint256 _escrowId)
external
view
returns (TimeLockRelease memory)
{
return timeLockReleases[_escrowId];
}
/**
* @dev Gets emergency release details
* @param _escrowId ID of the escrow
*/
function getEmergencyRelease(uint256 _escrowId)
external
view
returns (EmergencyRelease memory)
{
return emergencyReleases[_escrowId];
}
/**
* @dev Gets all escrows for a depositor
* @param _depositor Address of the depositor
*/
function getDepositorEscrows(address _depositor)
external
view
returns (uint256[] memory)
{
return depositorEscrows[_depositor];
}
/**
* @dev Gets all escrows for a beneficiary
* @param _beneficiary Address of the beneficiary
*/
function getBeneficiaryEscrows(address _beneficiary)
external
view
returns (uint256[] memory)
{
return beneficiaryEscrows[_beneficiary];
}
/**
* @dev Gets active escrows
*/
function getActiveEscrows()
external
view
returns (uint256[] memory)
{
uint256[] memory active = new uint256[](activeEscrows.length);
uint256 activeCount = 0;
for (uint256 i = 0; i < activeEscrows.length; i++) {
EscrowAccount storage escrow = escrowAccounts[activeEscrows[i]];
if (!escrow.isReleased && !escrow.isRefunded) {
active[activeCount] = activeEscrows[i];
activeCount++;
}
}
// Resize array to active count
assembly {
mstore(active, activeCount)
}
return active;
}
/**
* @dev Emergency pause function
*/
function pause() external onlyOwner {
_pause();
}
/**
* @dev Unpause function
*/
function unpause() external onlyOwner {
_unpause();
}
}