Last active
October 22, 2022 14:52
-
-
Save Reinis-FRP/6e4c7897ad984e0351b3944555475c4a to your computer and use it in GitHub Desktop.
Created using remix-ide: Realtime Ethereum Contract Compiler and Runtime. Load this file by pasting this gists URL or ID at https://remix.ethereum.org/#version=soljson-v0.8.7+commit.e28d00a7.js&optimize=false&runs=200&gist=6e4c7897ad984e0351b3944555475c4a
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: AGPL-3.0-only | |
pragma solidity ^0.8.0; | |
import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | |
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; | |
import "@uma/core/contracts/common/implementation/AddressWhitelist.sol"; | |
import "@uma/core/contracts/oracle/implementation/Constants.sol"; | |
import "@uma/core/contracts/oracle/interfaces/FinderInterface.sol"; | |
import "@uma/core/contracts/oracle/interfaces/OptimisticOracleV2Interface.sol"; | |
/** | |
* @title Insurance Arbitrator Contract | |
* @notice This example implementation allows insurer to issue insurance policy by depositing insured amount, | |
* designating the insured beneficiary and describing insured event. At any time anyone can submit claim that the insured | |
* event has occurred by posting oracle bonding. Insurance Arbitrator resolves the claim through Optimistic Oracle by | |
* passing templated question with insured event description in ancillary data. If the claim is confirmed this contract | |
* automatically pays out insurance coverage to the insured beneficiary. If the claim is rejected policy continues to be | |
* active ready for the subsequent claim attempts. | |
*/ | |
contract InsuranceArbitrator { | |
using SafeERC20 for IERC20; | |
/****************************************** | |
* STATE VARIABLES AND DATA STRUCTURES * | |
******************************************/ | |
// Stores state and parameters of insurance policy. | |
struct InsurancePolicy { | |
bool claimInitiated; // Claim state preventing simultaneous claim attempts. | |
string insuredEvent; // Short description of insured event. | |
address insuredAddress; // Beneficiary address eligible for insurance compensation. | |
uint256 insuredAmount; // Amount of insurance coverage. | |
} | |
// References all active insurance policies by policyId. | |
mapping(bytes32 => InsurancePolicy) public insurancePolicies; | |
// Maps hash of initiated claims to their policyId. | |
// This is used in callback function to potentially pay out the beneficiary. | |
mapping(bytes32 => bytes32) public insuranceClaims; | |
uint256 public constant oracleBondPercentage = 0.001e18; // Proposal bond set to 0.1% of claimed insurance coverage. | |
uint256 public constant optimisticOracleLivenessTime = 3600 * 24; // Optimistic oracle liveness set to 24h. | |
// Price identifier to use when requesting claims through Optimistic Oracle. | |
bytes32 public constant priceIdentifier = "YES_OR_NO_QUERY"; | |
// Template for constructing ancillary data. The claim would insert insuredEvent in between when requesting | |
// through Optimistic Oracle. | |
string constant ancillaryDataHead = 'q:"Had the following insured event occurred as of request timestamp: '; | |
string constant ancillaryDataTail = '?"'; | |
FinderInterface public finder; // Finder for UMA contracts. | |
OptimisticOracleV2Interface public immutable oo; // Optimistic Oracle instance where claims are resolved. | |
IERC20 public immutable currency; // Denomination token for insurance coverage and bonding. | |
uint256 public constant MAX_EVENT_DESCRIPTION_SIZE = 300; // Insured event description should be concise. | |
/**************************************** | |
* EVENTS * | |
****************************************/ | |
event PolicyIssued( | |
bytes32 indexed policyId, | |
address indexed insurer, | |
string insuredEvent, | |
address indexed insuredAddress, | |
uint256 insuredAmount | |
); | |
event ClaimSubmitted(uint256 claimTimestamp, bytes32 indexed claimId, bytes32 indexed policyId); | |
event ClaimAccepted(bytes32 indexed claimId, bytes32 indexed policyId); | |
event ClaimRejected(bytes32 indexed claimId, bytes32 indexed policyId); | |
/** | |
* @notice Construct the InsuranceArbitrator | |
* @param _finderAddress DVM finder to find other UMA ecosystem contracts. | |
* @param _currency denomination token for insurance coverage and bonding. | |
*/ | |
constructor(address _finderAddress, address _currency) { | |
finder = FinderInterface(_finderAddress); | |
currency = IERC20(_currency); | |
oo = OptimisticOracleV2Interface(finder.getImplementationAddress(OracleInterfaces.OptimisticOracleV2)); | |
} | |
/****************************************** | |
* INSURANCE FUNCTIONS * | |
******************************************/ | |
/** | |
* @notice Deposits insuredAmount from the insurer and issues insurance policy to the insured beneficiary. | |
* @dev This contract must be approved to spend at least insuredAmount of currency token. | |
* @param insuredEvent short description of insured event. Potential verifiers should be able to evaluate whether | |
* this event had occurred as of claim time with binary yes/no answer. | |
* @param insuredAddress Beneficiary address eligible for insurance compensation. | |
* @param insuredAmount Amount of insurance coverage. | |
* @return policyId Unique identifier of issued insurance policy. | |
*/ | |
function issueInsurance( | |
string calldata insuredEvent, | |
address insuredAddress, | |
uint256 insuredAmount | |
) external returns (bytes32 policyId) { | |
require(bytes(insuredEvent).length <= MAX_EVENT_DESCRIPTION_SIZE, "Event description too long"); | |
require(insuredAddress != address(0), "Invalid insured address"); | |
require(insuredAmount > 0, "Amount should be above 0"); | |
policyId = _getPolicyId(block.number, insuredEvent, insuredAddress, insuredAmount); | |
require(insurancePolicies[policyId].insuredAddress == address(0), "Policy already issued"); | |
InsurancePolicy storage newPolicy = insurancePolicies[policyId]; | |
newPolicy.insuredEvent = insuredEvent; | |
newPolicy.insuredAddress = insuredAddress; | |
newPolicy.insuredAmount = insuredAmount; | |
currency.safeTransferFrom(msg.sender, address(this), insuredAmount); | |
emit PolicyIssued(policyId, msg.sender, insuredEvent, insuredAddress, insuredAmount); | |
} | |
/** | |
* @notice Anyone can submit insurance claim posting oracle bonding. Only one simultaneous claim per insurance | |
* policy is allowed. | |
* @dev This contract must be approved to spend at least (insuredAmount * oracleBondPercentage + finalFee) of | |
* currency token. This call requests and proposes that insuredEvent had ocured through Optimistic Oracle. | |
* @param policyId Identifier of claimed insurance policy. | |
*/ | |
function submitClaim(bytes32 policyId) external { | |
InsurancePolicy storage claimedPolicy = insurancePolicies[policyId]; | |
require(claimedPolicy.insuredAddress != address(0), "Insurance not issued"); | |
require(!claimedPolicy.claimInitiated, "Claim already initiated"); | |
claimedPolicy.claimInitiated = true; | |
uint256 timestamp = block.timestamp; | |
bytes memory ancillaryData = abi.encodePacked(ancillaryDataHead, claimedPolicy.insuredEvent, ancillaryDataTail); | |
bytes32 claimId = _getClaimId(timestamp, ancillaryData); | |
insuranceClaims[claimId] = policyId; | |
// Initiate price request at Optimistic Oracle. | |
oo.requestPrice(priceIdentifier, timestamp, ancillaryData, currency, 0); | |
// Configure price request parameters. | |
uint256 proposerBond = (claimedPolicy.insuredAmount * oracleBondPercentage) / 1e18; | |
uint256 totalBond = oo.setBond(priceIdentifier, timestamp, ancillaryData, proposerBond); | |
oo.setCustomLiveness(priceIdentifier, timestamp, ancillaryData, optimisticOracleLivenessTime); | |
oo.setCallbacks(priceIdentifier, timestamp, ancillaryData, false, false, true); | |
// Propose canonical value representing "True"; i.e. the insurance claim is valid. | |
currency.safeTransferFrom(msg.sender, address(this), totalBond); | |
currency.safeApprove(address(oo), totalBond); | |
oo.proposePriceFor(msg.sender, address(this), priceIdentifier, timestamp, ancillaryData, int256(1e18)); | |
emit ClaimSubmitted(timestamp, claimId, policyId); | |
} | |
/****************************************** | |
* CALLBACK FUNCTIONS * | |
******************************************/ | |
/** | |
* @notice Callback function called by the Optimistic Oracle when the claim is settled. If the claim is confirmed | |
* this pays out insurance coverage to the insured beneficiary and deletes the insurance policy. If the claim is | |
* rejected policy claim state is reset so that it is ready for the subsequent claim attempts. | |
* @param timestamp Timestamp of the price being requested. | |
* @param ancillaryData Ancillary data of the price being requested. | |
* @param price Price that was resolved by the escalation process. | |
*/ | |
function priceSettled( | |
bytes32, // identifier passed by Optimistic Oracle, but not used here as it is always the same. | |
uint256 timestamp, | |
bytes memory ancillaryData, | |
int256 price | |
) external { | |
bytes32 claimId = _getClaimId(timestamp, ancillaryData); | |
require(address(oo) == msg.sender, "Unauthorized callback"); | |
// Claim can be settled only once, thus should be deleted. | |
bytes32 policyId = insuranceClaims[claimId]; | |
InsurancePolicy memory claimedPolicy = insurancePolicies[policyId]; | |
delete insuranceClaims[claimId]; | |
// Deletes insurance policy and transfers claim amount if the claim was confirmed. | |
if (price == 1e18) { | |
delete insurancePolicies[policyId]; | |
currency.safeTransfer(claimedPolicy.insuredAddress, claimedPolicy.insuredAmount); | |
emit ClaimAccepted(claimId, policyId); | |
// Otherwise just reset the flag so that repeated claims can be made. | |
} else { | |
insurancePolicies[policyId].claimInitiated = false; | |
emit ClaimRejected(claimId, policyId); | |
} | |
} | |
/****************************************** | |
* INTERNAL FUNCTIONS * | |
******************************************/ | |
function _getPolicyId( | |
uint256 blockNumber, | |
string memory insuredEvent, | |
address insuredAddress, | |
uint256 insuredAmount | |
) internal pure returns (bytes32) { | |
return keccak256(abi.encode(blockNumber, insuredEvent, insuredAddress, insuredAmount)); | |
} | |
function _getClaimId(uint256 timestamp, bytes memory ancillaryData) internal pure returns (bytes32) { | |
return keccak256(abi.encode(timestamp, ancillaryData)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment