Skip to content

Commit

Permalink
Treat fees as pending until ritual is final
Browse files Browse the repository at this point in the history
Either ritual is successful and fees can be used by the protocol, or ritual is failed and fees are refunded to initiator (consider partial refund)
  • Loading branch information
cygnusv committed Jun 22, 2023
1 parent 47f1c06 commit ab7478e
Showing 1 changed file with 39 additions and 3 deletions.
42 changes: 39 additions & 3 deletions contracts/contracts/coordination/Coordinator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ contract Coordinator is AccessControlDefaultAdminRules {
bool public isInitiationPublic;
IFeeModel feeModel; // TODO: Consider making feeModel specific to each ritual
IReimbursementPool reimbursementPool;
uint256 public totalPendingFees;
mapping(uint256 => uint256) public pendingFees;

constructor(
IAccessControlApplication _stakes,
Expand Down Expand Up @@ -162,9 +164,10 @@ contract Coordinator is AccessControlDefaultAdminRules {
require(2 <= length && length <= maxDkgSize, "Invalid number of nodes");
require(duration > 0, "Invalid ritual duration"); // TODO: We probably want to restrict it more

processRitualPayment(providers, duration);

uint32 id = uint32(rituals.length);

processRitualPayment(id, providers, duration);

Ritual storage ritual = rituals.push();
ritual.initiator = msg.sender;
ritual.authority = authority;
Expand Down Expand Up @@ -293,6 +296,8 @@ contract Coordinator is AccessControlDefaultAdminRules {
// TODO: Consider including public key in event
}
}

processPendingFee(ritualId);
processReimbursement(initialGasLeft);
}

Expand All @@ -318,14 +323,45 @@ contract Coordinator is AccessControlDefaultAdminRules {
return getParticipantFromProvider(rituals[ritualID], provider);
}

function processRitualPayment(address[] calldata providers, uint32 duration) internal {
function processRitualPayment(uint256 ritualID, address[] calldata providers, uint32 duration) internal {
uint256 ritualCost = feeModel.getRitualInitiationCost(providers, duration);
if (ritualCost > 0){
totalPendingFees += ritualCost;
assert(pendingFees[ritualID] == 0); // TODO: This is an invariant, not sure if actually needed
pendingFees[ritualID] += ritualCost;
IERC20 currency = IERC20(feeModel.currency());
currency.transferFrom(msg.sender, address(this), ritualCost);
// TODO: Define methods to manage these funds
}
}

function processPendingFee(uint256 ritualID) public {
Ritual storage ritual = rituals[ritualID];
RitualState state = getRitualState(ritual);
require(
state == RitualState.TIMEOUT ||
state == RitualState.INVALID ||
state == RitualState.FINALIZED,
"Ritual is not ended"
);
uint256 pending = pendingFees[ritualID];
require(pending > 0, "No pending fees for this ritual");

// Finalize fees for this ritual
totalPendingFees -= pending;
delete pendingFees[ritualID];
// Transfer fees back to initiator if failed
if(state == RitualState.TIMEOUT || state == RitualState.INVALID){
// Amount to refund depends on how much work nodes did for the ritual.
// TODO: Validate if this is enough to remove griefing attacks
uint256 executedTransactions = ritual.totalTranscripts + ritual.totalAggregations;
uint256 expectedTransactions = 2 * ritual.dkgSize;
uint256 consumedFee = pending * executedTransactions / expectedTransactions;
uint256 refundableFee = pending - consumedFee;
IERC20 currency = IERC20(feeModel.currency());
currency.transferFrom(address(this), ritual.initiator, refundableFee);
}
}

function processReimbursement(uint256 initialGasLeft) internal {
if(address(reimbursementPool) != address(0)){ // TODO: Consider defining a method
Expand Down

0 comments on commit ab7478e

Please sign in to comment.