Skip to content

Ensure oracle fees are paid independently#3899

Open
flopez7 wants to merge 8 commits into
developfrom
flopez/oracle-fees
Open

Ensure oracle fees are paid independently#3899
flopez7 wants to merge 8 commits into
developfrom
flopez/oracle-fees

Conversation

@flopez7
Copy link
Copy Markdown
Contributor

@flopez7 flopez7 commented May 13, 2026

Issue tracking

#3880

Context behind the change

Oracle fees were previously affected when worker submissions were rejected and no worker payout was made. This change separates oracle compensation from worker payouts so oracle fees are reserved from the funded amount and paid when the escrow is completed or canceled, while rejected submissions still receive no worker payout. The SDK, oracles, and subgraph were updated to use and track the corrected fund amounts and oracle fee transfers.

How has this been tested?

Deployed locally

Release plan

Deploy new contract version, SDKs and subgraph

Potential risks; What to monitor; Rollback plan

Check bulkpayout balance and fees.

@flopez7 flopez7 requested a review from portuu3 May 13, 2026 09:06
@flopez7 flopez7 self-assigned this May 13, 2026
@vercel
Copy link
Copy Markdown

vercel Bot commented May 13, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
human-app Ready Ready Preview, Comment May 15, 2026 10:42am
human-dashboard-frontend Ready Ready Preview, Comment May 15, 2026 10:42am
staking-dashboard Ready Ready Preview, Comment May 15, 2026 10:42am
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
faucet-frontend Ignored Ignored Preview May 15, 2026 10:42am
faucet-server Ignored Ignored Preview May 15, 2026 10:42am

Request Review

@flopez7 flopez7 added the WIP Work In Progress label May 13, 2026
@vercel vercel Bot temporarily deployed to Preview – human-app May 13, 2026 12:33 Inactive
@vercel vercel Bot temporarily deployed to Preview – staking-dashboard May 13, 2026 12:33 Inactive
@vercel vercel Bot temporarily deployed to Preview – human-dashboard-frontend May 13, 2026 12:33 Inactive
chainId: number,
escrowAddress: string,
): Promise<bigint> {
const fundAmount = await escrowClient.getFundAmount(escrowAddress);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can use totalFundAmount from escrow data obtained in the next line.

@vercel vercel Bot temporarily deployed to Preview – staking-dashboard May 13, 2026 13:38 Inactive
@vercel vercel Bot temporarily deployed to Preview – human-app May 13, 2026 13:38 Inactive
@vercel vercel Bot temporarily deployed to Preview – human-dashboard-frontend May 13, 2026 13:38 Inactive
@vercel vercel Bot temporarily deployed to Preview – staking-dashboard May 14, 2026 08:46 Inactive
@vercel vercel Bot temporarily deployed to Preview – human-dashboard-frontend May 14, 2026 08:46 Inactive
@vercel vercel Bot temporarily deployed to Preview – human-app May 14, 2026 08:46 Inactive
Comment thread packages/core/contracts/Escrow.sol Outdated
Comment on lines +481 to +484
require(_recipients.length == _amounts.length, 'Length mismatch');
require(_amounts.length > 0, 'Empty amounts');
require(_recipients.length <= BULK_MAX_COUNT, 'Too many recipients');
uint256 length = _amounts.length;
require(length > 0, 'Empty amounts');
require(length <= BULK_MAX_COUNT, 'Too many recipients');
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
require(_recipients.length == _amounts.length, 'Length mismatch');
require(_amounts.length > 0, 'Empty amounts');
require(_recipients.length <= BULK_MAX_COUNT, 'Too many recipients');
uint256 length = _amounts.length;
require(length > 0, 'Empty amounts');
require(length <= BULK_MAX_COUNT, 'Too many recipients');
uint256 length = _amounts.length;
require(_recipients.length == length, 'Length mismatch');
require(length > 0, 'Empty amounts');
require(length <= BULK_MAX_COUNT, 'Too many recipients');

Comment on lines 325 to 373
EscrowStatuses _status = status;
uint256 _remaining = remainingFunds;
uint256 _remainingFunds = remainingFunds;

uint256 _reputationOracleFee = (fundAmount *
reputationOracleFeePercentage) / 100;
uint256 _recordingOracleFee = (fundAmount *
recordingOracleFeePercentage) / 100;
uint256 _exchangeOracleFee = (fundAmount *
exchangeOracleFeePercentage) / 100;

IERC20 tokenContract = IERC20(token);

if (_remaining > 0) {
IERC20 tokenContract = IERC20(token);
tokenContract.safeTransfer(launcher, _remaining);
fundAmount = 0;
remainingFunds = 0;
reservedFunds = 0;

if (_reputationOracleFee > 0) {
tokenContract.safeTransfer(reputationOracle, _reputationOracleFee);
}
if (_recordingOracleFee > 0) {
tokenContract.safeTransfer(recordingOracle, _recordingOracleFee);
}
if (_exchangeOracleFee > 0) {
tokenContract.safeTransfer(exchangeOracle, _exchangeOracleFee);
}
if (
_reputationOracleFee > 0 ||
_recordingOracleFee > 0 ||
_exchangeOracleFee > 0
) {
address[] memory oracles = new address[](3);
uint256[] memory amounts = new uint256[](3);

oracles[0] = reputationOracle;
oracles[1] = recordingOracle;
oracles[2] = exchangeOracle;

amounts[0] = _reputationOracleFee;
amounts[1] = _recordingOracleFee;
amounts[2] = _exchangeOracleFee;

emit OracleFeeTransfer(oracles, amounts);
}
if (_remainingFunds > 0) {
tokenContract.safeTransfer(launcher, _remainingFunds);
if (_status == EscrowStatuses.ToCancel) {
emit CancellationRefund(_remaining);
emit CancellationRefund(_remainingFunds);
}
remainingFunds = 0;
reservedFunds = 0;
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
EscrowStatuses _status = status;
uint256 _remaining = remainingFunds;
uint256 _remainingFunds = remainingFunds;
uint256 _reputationOracleFee = (fundAmount *
reputationOracleFeePercentage) / 100;
uint256 _recordingOracleFee = (fundAmount *
recordingOracleFeePercentage) / 100;
uint256 _exchangeOracleFee = (fundAmount *
exchangeOracleFeePercentage) / 100;
IERC20 tokenContract = IERC20(token);
if (_remaining > 0) {
IERC20 tokenContract = IERC20(token);
tokenContract.safeTransfer(launcher, _remaining);
fundAmount = 0;
remainingFunds = 0;
reservedFunds = 0;
if (_reputationOracleFee > 0) {
tokenContract.safeTransfer(reputationOracle, _reputationOracleFee);
}
if (_recordingOracleFee > 0) {
tokenContract.safeTransfer(recordingOracle, _recordingOracleFee);
}
if (_exchangeOracleFee > 0) {
tokenContract.safeTransfer(exchangeOracle, _exchangeOracleFee);
}
if (
_reputationOracleFee > 0 ||
_recordingOracleFee > 0 ||
_exchangeOracleFee > 0
) {
address[] memory oracles = new address[](3);
uint256[] memory amounts = new uint256[](3);
oracles[0] = reputationOracle;
oracles[1] = recordingOracle;
oracles[2] = exchangeOracle;
amounts[0] = _reputationOracleFee;
amounts[1] = _recordingOracleFee;
amounts[2] = _exchangeOracleFee;
emit OracleFeeTransfer(oracles, amounts);
}
if (_remainingFunds > 0) {
tokenContract.safeTransfer(launcher, _remainingFunds);
if (_status == EscrowStatuses.ToCancel) {
emit CancellationRefund(_remaining);
emit CancellationRefund(_remainingFunds);
}
remainingFunds = 0;
reservedFunds = 0;
}
EscrowStatuses _status = status;
uint256 _remainingFunds = remainingFunds;
uint256 _reputationOracleFee = (fundAmount *
reputationOracleFeePercentage) / 100;
uint256 _recordingOracleFee = (fundAmount *
recordingOracleFeePercentage) / 100;
uint256 _exchangeOracleFee = (fundAmount *
exchangeOracleFeePercentage) / 100;
IERC20 tokenContract = IERC20(token);
fundAmount = 0;
remainingFunds = 0;
reservedFunds = 0;
address[] memory oracles = new address[](3);
uint256[] memory amounts = new uint256[](3);
oracles[0] = reputationOracle;
oracles[1] = recordingOracle;
oracles[2] = exchangeOracle;
amounts[0] = _reputationOracleFee;
amounts[1] = _recordingOracleFee;
amounts[2] = _exchangeOracleFee;
if (_reputationOracleFee > 0) {
tokenContract.safeTransfer(reputationOracle, _reputationOracleFee);
}
if (_recordingOracleFee > 0) {
tokenContract.safeTransfer(recordingOracle, _recordingOracleFee);
}
if (_exchangeOracleFee > 0) {
tokenContract.safeTransfer(exchangeOracle, _exchangeOracleFee);
}
emit OracleFeeTransfer(oracles, amounts);
if (_remainingFunds > 0) {
tokenContract.safeTransfer(launcher, _remainingFunds);
if (_status == EscrowStatuses.ToCancel) {
emit CancellationRefund(_remainingFunds);
}
}

reservedFunds = 0;
}

if (_status == EscrowStatuses.ToCancel) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if no payout happened the oracles should not get any fee

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

WIP Work In Progress

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants