Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion contracts/interfaces/IAssessment.sol
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ interface IAssessment {
event StakeWithdrawn(address indexed user, uint96 amount);
event VoteCast(address indexed user, uint96 stakedAmount, bool accepted);
event RewardWithdrawn(address user, uint256 amount);
event FraudResolution(uint assessmentId, address assessor, Poll poll);
event FraudProcessed(uint assessmentId, address assessor, Poll poll);
event FraudSubmitted(bytes32 root);

}
4 changes: 2 additions & 2 deletions contracts/interfaces/IIndividualClaims.sol
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,8 @@ interface IIndividualClaims {

/* ========== EVENTS ========== */

event ClaimSubmitted(address user, uint104 claimId, uint32 coverId, uint24 productId);
event ClaimSubmitted(address user, uint claimId, uint coverId, uint productId);
event MetadataSubmitted(uint indexed claimId, string ipfsMetadata);
event ClaimPayoutRedeemed(address indexed user, uint256 amount, uint104 claimId);
event ClaimPayoutRedeemed(address indexed user, uint amount, uint claimId, uint coverId);

}
6 changes: 3 additions & 3 deletions contracts/interfaces/IYieldTokenIncidents.sol
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,8 @@ interface IYieldTokenIncidents {

/* ========== EVENTS ========== */

event IncidentSubmitted(address user, uint104 incidentId, uint24 productId);
event MetadataSubmitted(uint indexed incidentId, uint expectedPayoutInNXM, string ipfsMetadata);
event IncidentPayoutRedeemed(address indexed user, uint256 amount, uint104 incidentId, uint24 productId);
event IncidentSubmitted(address user, uint incidentId, uint productId, uint expectedPayoutInNXM);
event MetadataSubmitted(uint indexed incidentId, string ipfsMetadata);
event IncidentPayoutRedeemed(address indexed user, uint amount, uint incidentId, uint coverId);

}
17 changes: 16 additions & 1 deletion contracts/libraries/SafeUintCast.sol
Original file line number Diff line number Diff line change
Expand Up @@ -311,10 +311,25 @@ library SafeUintCast {
* - input must fit into 96 bits
*/
function toUint96(uint256 value) internal pure returns (uint96) {
require(value < 2**96, "SafeCast: value doesn\'t fit in 104 bits");
require(value < 2**96, "SafeCast: value doesn\'t fit in 96 bits");
return uint96(value);
}

/**
* @dev Returns the downcasted uint80 from uint256, reverting on
* overflow (when the input is greater than largest uint80).
*
* Counterpart to Solidity's `uint104` operator.
*
* Requirements:
*
* - input must fit into 80 bits
*/
function toUint80(uint256 value) internal pure returns (uint80) {
require(value < 2**80, "SafeCast: value doesn\'t fit in 80 bits");
return uint80(value);
}

/**
* @dev Returns the downcasted uint64 from uint256, reverting on
* overflow (when the input is greater than largest uint64).
Expand Down
17 changes: 10 additions & 7 deletions contracts/modules/assessment/Assessment.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import "../../interfaces/IMemberRoles.sol";
import "../../interfaces/INXMToken.sol";
import "../../interfaces/ITokenController.sol";
import "../../libraries/Math.sol";
import "../../libraries/SafeUintCast.sol";

/// Provides the assessment mechanism for members to decide the outcome of the events that can lead
/// to payouts. Mints rewards for stakers that act benevolently and allows burning fraudulent ones.
Expand Down Expand Up @@ -156,8 +157,8 @@ contract Assessment is IAssessment, MasterAwareV2 {
);
}

nxm.transfer(to, amount);
stakeOf[msg.sender].amount -= amount;
nxm.transfer(to, amount);
}

/// Withdraws a staker's accumulated rewards to a destination address but only the staker can
Expand Down Expand Up @@ -231,7 +232,7 @@ contract Assessment is IAssessment, MasterAwareV2 {
}

// This is the index where the next withdrawReward call will start iterating from
stakeOf[staker].rewardsWithdrawableFromIndex = uint104(withdrawnUntilIndex);
stakeOf[staker].rewardsWithdrawableFromIndex = SafeUintCast.toUint104(withdrawnUntilIndex);
ITokenController(getInternalContractAddress(ID.TC)).mint(destination, withdrawn);
}

Expand Down Expand Up @@ -319,8 +320,8 @@ contract Assessment is IAssessment, MasterAwareV2 {
"At least one accept vote is required to vote deny"
);

if (isAcceptVote && poll.accepted == 0) {
// Reset the poll end when the first accepted vote
if (poll.accepted == 0) {
// Reset the poll end date on the first accept vote
poll.end = uint32(block.timestamp + config.minVotingPeriodInDays * 1 days);
}

Expand Down Expand Up @@ -363,6 +364,7 @@ contract Assessment is IAssessment, MasterAwareV2 {
/// @param root The merkle tree root hash
function submitFraud(bytes32 root) external override onlyGovernance {
fraudResolution.push(root);
emit FraudSubmitted(root);
}

/// Allows anyone to undo fraudulent votes and burn the fraudulent assessors present in the
Expand Down Expand Up @@ -424,11 +426,12 @@ contract Assessment is IAssessment, MasterAwareV2 {
}

// If the poll ends in less than 24h, extend it to 24h
if (poll.end < uint32(block.timestamp) + 1 days) {
poll.end = uint32(block.timestamp) + 1 days;
uint32 nextDay = uint32(block.timestamp + 1 days);
if (poll.end < nextDay) {
poll.end = nextDay;
}

emit FraudResolution(vote.assessmentId, assessor, poll);
emit FraudProcessed(vote.assessmentId, assessor, poll);
assessments[vote.assessmentId].poll = poll;
}

Expand Down
93 changes: 51 additions & 42 deletions contracts/modules/assessment/IndividualClaims.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import "../../interfaces/IMemberRoles.sol";
import "../../interfaces/INXMToken.sol";
import "../../interfaces/IPool.sol";
import "../../libraries/Math.sol";
import "../../libraries/SafeUintCast.sol";

/// Provides a way for cover owners to submit claims and redeem payouts. It is an entry point to
/// the assessment process where the members of the mutual decide the outcome of claims.
Expand Down Expand Up @@ -112,7 +113,7 @@ contract IndividualClaims is IIndividualClaims, MasterAwareV2 {
MIN_ASSESSMENT_DEPOSIT_DENOMINATOR;

// If dynamicDeposit falls below minDeposit use minDeposit instead
uint assessmentDepositInETH = minDeposit > dynamicDeposit ? minDeposit : dynamicDeposit;
uint assessmentDepositInETH = Math.max(minDeposit, dynamicDeposit);

return (assessmentDepositInETH, totalRewardInNXM);
}
Expand All @@ -127,16 +128,16 @@ contract IndividualClaims is IIndividualClaims, MasterAwareV2 {
Claim memory claim = claims[id];
(IAssessment.Poll memory poll,,) = assessment().assessments(claim.assessmentId);

ClaimStatus claimStatus;
PayoutStatus payoutStatus;
ClaimStatus claimStatus = ClaimStatus.PENDING;
PayoutStatus payoutStatus = PayoutStatus.PENDING;
{
// Determine the claims status
if (block.timestamp < poll.end) {
claimStatus = ClaimStatus.PENDING;
} else if (poll.accepted > poll.denied) {
claimStatus = ClaimStatus.ACCEPTED;
} else {
claimStatus = ClaimStatus.DENIED;
if (block.timestamp >= poll.end) {
if (poll.accepted > poll.denied) {
claimStatus = ClaimStatus.ACCEPTED;
} else {
claimStatus = ClaimStatus.DENIED;
}
}

// Determine the payout status
Expand All @@ -151,14 +152,10 @@ contract IndividualClaims is IIndividualClaims, MasterAwareV2 {
config.payoutRedemptionPeriodInDays * 1 days
) {
payoutStatus = PayoutStatus.UNCLAIMED;
} else {
payoutStatus = PayoutStatus.PENDING;
}
}
} else if (claimStatus == ClaimStatus.DENIED) {
payoutStatus = PayoutStatus.DENIED;
} else {
payoutStatus = PayoutStatus.PENDING;
}
}

Expand Down Expand Up @@ -245,11 +242,12 @@ contract IndividualClaims is IIndividualClaims, MasterAwareV2 {
uint80 assessmentId = claims[previousSubmission.claimId].assessmentId;
IAssessment.Poll memory poll = assessment().getPoll(assessmentId);
(,,uint8 payoutCooldownInDays,) = assessment().config();
if (block.timestamp >= poll.end + payoutCooldownInDays * 1 days) {
uint payoutCooldown = payoutCooldownInDays * 1 days;
if (block.timestamp >= poll.end + payoutCooldown) {
if (
poll.accepted > poll.denied &&
block.timestamp < poll.end +
payoutCooldownInDays * 1 days +
payoutCooldown+
config.payoutRedemptionPeriodInDays * 1 days
) {
revert("A payout can still be redeemed");
Expand Down Expand Up @@ -279,43 +277,56 @@ contract IndividualClaims is IIndividualClaims, MasterAwareV2 {
segment.start + segment.period + productType.gracePeriodInDays * 1 days > block.timestamp,
"Cover is outside the grace period"
);
}

Claim memory claim = Claim(
0,
coverId,
segmentId,
requestedAmount,
coverData.payoutAsset,
false // payoutRedeemed
);
emit ClaimSubmitted(
msg.sender, // user
claims.length, // claimId
coverId, // coverId
coverData.productId // user
);
}

(uint assessmentDepositInETH, uint totalRewardInNXM) = getAssessmentDepositAndReward(
requestedAmount,
segment.period,
coverData.payoutAsset
);

uint newAssessmentId = assessment().startAssessment(totalRewardInNXM, assessmentDepositInETH);

Claim memory claim = Claim({
assessmentId: SafeUintCast.toUint80(newAssessmentId),
coverId: coverId,
segmentId: segmentId,
amount: requestedAmount,
payoutAsset: coverData.payoutAsset,
payoutRedeemed: false
});
claims.push(claim);

if (bytes(ipfsMetadata).length > 0) {
emit MetadataSubmitted(claims.length - 1, ipfsMetadata);
}


require(msg.value >= assessmentDepositInETH, "Assessment deposit is insufficient");
if (msg.value > assessmentDepositInETH) {
// Refund ETH excess back to the sender
(bool refunded, /* bytes data */) = msg.sender.call{value: msg.value - assessmentDepositInETH}("");
(
bool refunded,
/* bytes data */
) = msg.sender.call{value: msg.value - assessmentDepositInETH}("");
require(refunded, "Assessment deposit excess refund failed");
}

// Transfer the assessment deposit to the pool
(bool transferSucceeded, /* bytes data */) = getInternalContractAddress(ID.P1).call{value: assessmentDepositInETH}("");
(
bool transferSucceeded,
/* bytes data */
) = getInternalContractAddress(ID.P1).call{value: assessmentDepositInETH}("");
require(transferSucceeded, "Assessment deposit transfer to pool failed");

uint newAssessmentId = assessment().startAssessment(totalRewardInNXM, assessmentDepositInETH);
claim.assessmentId = uint80(newAssessmentId);
claims.push(claim);

if (bytes(ipfsMetadata).length > 0) {
emit MetadataSubmitted(claims.length - 1, ipfsMetadata);
}

return (claim);
return claim;
}

/// Redeems payouts for accepted claims
Expand All @@ -336,15 +347,12 @@ contract IndividualClaims is IIndividualClaims, MasterAwareV2 {
require(poll.accepted > poll.denied, "The claim needs to be accepted");

(,,uint8 payoutCooldownInDays,) = assessment().config();
require(
block.timestamp >= poll.end + payoutCooldownInDays * 1 days,
"The claim is in cooldown period"
);
uint payoutCooldown = payoutCooldownInDays * 1 days;

require(block.timestamp >= poll.end + payoutCooldown, "The claim is in cooldown period");

require(
block.timestamp < poll.end +
payoutCooldownInDays * 1 days +
config.payoutRedemptionPeriodInDays * 1 days,
block.timestamp < poll.end + payoutCooldown + config.payoutRedemptionPeriodInDays * 1 days,
"The redemption period has expired"
);

Expand All @@ -369,6 +377,7 @@ contract IndividualClaims is IIndividualClaims, MasterAwareV2 {
poolContract.sendPayout(claim.payoutAsset, coverOwner, claim.amount);
}

emit ClaimPayoutRedeemed(coverOwner, claim.amount, claimId, claim.coverId);
}

/// Updates configurable aprameters through governance
Expand Down
Loading