Insurance Claim Arbitration
Using the Optimistic Oracle to allow for verification of insurance claims
This section covers the insurance claims arbitration contract, which is available in the developer's quick-start repo. This tutorial shows an example of how insurance claims can be resolved and settled through the Optimistic Oracle V2 contract.
You will find out how to test and deploy this smart contract and how it integrates with the Optimistic Oracle.
Insurance Arbitrator Contract
This smart contract allows insurers to issue insurance policies by depositing the insured amount, designating the insured beneficiary, and describing the insured event.
Anyone can submit a claim that the insured event has occurred at any time. Insurance Arbitrators resolve the claim through the Optimistic Oracle by passing a question with a description of the insured event in ancillary data using the YES_OR_NO_QUERY
price identifier specified in UMIP-107.
If the claim is confirmed and settled through the Optimistic Oracle, this contract automatically pays out insurance coverage to the beneficiary. If the claim is rejected, the policy continues to be active and ready for subsequent claim attempts.
Development environment and tests
Clone repository and Install dependencies
Clone the UMA dev-quickstart repository and install the dependencies. To install dependencies, you will need to install the long-term support version of nodejs, currently Nodejs v16, and yarn. You can then install dependencies by running yarn with no arguments:
Compiling your contracts
We will need to run the following command to compile the contracts and make the Typescript interfaces so that they are easy to work with:
Contract implementation
The contract discussed in this tutorial can be found at dev-quickstart/contracts/InsuranceArbitrator.sol
(here) within the repo.
Contract creation and initialization
_finder
parameter in the constructor points the Insurance Arbitrator to the Finder contract that stores the addresses of the rest of the UMA contracts. The Finder address can be fetched from the relevant networks file, if you are on a live network, or you can provide your own Finder
instance if deploying UMA protocol in your own sandboxed testing environment.
_currency
parameter in the constructor identifies the token used for settlement of insurance claims, as well as the bond currency for proposals and disputes. This token should be approved as whitelisted UMA collateral. Please check Approved Collateral Types for production networks or call getWhitelist()
on the Address Whitelist contract for any of the test networks.
Alternatively, you can approve a new token address with addToWhitelist
method in the Address Whitelist contract if working in a sandboxed UMA environment.
_timer
is used only when running unit tests locally to simulate the advancement of time. For all the public networks (including testnets) the zero address should be used.
As part of initialization, the oo
variable is set to the address of the OptimisticOracleV2
implementation as discovered through getImplementationAddress
method in the Finder contract.
Issuing insurance
issueInsurance
method allows any insurer to deposit insuredAmount
of currency
tokens by designating an insurance beneficiary (insuredAddress
) and defining the insured event (insuredEvent
). Before calling this method, the insurer should have approved this contract to spend the required amount of currency
tokens.
Internally, the issued policy is stored in the insurancePolicies
mapping using the calculated policyId
key that is generated by hashing the current block number with the provided insurance parameters in the internal _getPolicyId
function.
After pulling insuredAmount
from the caller in the issueInsurance
method, the contract emits a PolicyIssued
event including the policyId
parameter that should be used when claiming insurance.
Submitting insurance claim
Anyone can submit an insurance claim on the issued policy by calling the submitClaim
method with the relevant policyId
parameter. This method will initiate both a data request and proposal with the Optimistic Oracle. A proposal bond is required, hence the caller should have approved this contract to spend the required amount of currency
tokens for the proposal bond.
After checking that the policyId
represents a valid unclaimed insurance policy, the contract gets the current timestamp
and composes ancillaryData
that will be required for making requests and proposals to the Optimistic Oracle:
The resulting timestamp
and ancillaryData
parameters are hashed in the internal _getClaimId
method that is used as a key when storing the linked policyId
in the insuranceClaims
mapping. This information will be required when receiving a callback from the Optimistic Oracle.
The concatenated ancillaryData
will have a valid question as specified in UMIP-107 for YES_OR_NO_QUERY
price identifier.
An Optimistic Oracle data request is initiated without providing any proposer reward since the proposal will be done within the submitClaim
method:
Before the proposal is made, the Optimistic Oracle allows the requesting contract (this Insurance Arbitrator) to set additional parameters like bonding, liveness, and callback settings. This requires passing the same priceIdentifier
, timestamp
, and ancillaryData
parameters to identify the request.
Total bond to be pulled from the claim initiator consists of the Optimistic Oracle proposer bond and final fee for the relevant currency
token. This contract sets the proposer bond as a fixed percentage (constant oracleBondPercentage
) from insuredAmount
. When calling the setBond
method, the Optimistic Oracle calculates and returns the total bond that would be pulled when making the proposal:
Optimistic Oracle liveness is set by calling the setCustomLiveness
method. This contract uses 24 hours so that verifiers have sufficient time to check the claim, but one can adjust the optimisticOracleLivenessTime
constant for testing. (You probably don't want to wait a full day to resolve your test requests!)
In contrast to earlier versions, the Optimistic Oracle V2 by default does not use callbacks and requesting contracts have to explicitly subscribe to them if intending to perform any logic when a data request has changed state. Here, in calling setCallbacks
, this contract only subscribes to a callback for settlement, as implemented in the priceSettled
method. (Note: Subscribing to any other callbacks that are not implemented in the requesting contract would make data requests unresolvable.)
After totalBond
amount of currency
token is pulled from the claim initiator and approved to be taken by Optimistic Oracle, this contract proposes 1e18
representing an answer of YES
to the raised question. Requesting and proposing affirmative answers atomically allows us to reduce the number of steps taken by end users and it is most likely expected that the insured beneficiary would be initiating the claim.
Disputing insurance claim
For the sake of simplicity this contract does not implement a dispute method, but the disputer can dispute the submitted claim directly through Optimistic Oracle before the liveness passes by calling its disputePrice
method:
The disputer should pass the address of this Insurance Arbitrator contract as requester
and all the other parameters from the original request when the claim was initiated as emitted by the Optimistic Oracle in its RequestPrice
event.
If the claim is disputed, the request is escalated to the UMA DVM and it can be settled only after UMA voters have resolved it. To learn more about the DVM, see the docs section on the DVM: how does UMA's DVM work.
Settling insurance claim
Similar to disputes, claim settlement should be initiated through the Optimistic Oracle contract by calling its settle
method with the same parameters:
In case the liveness has expired or a dispute has been resolved by the UMA DVM, this call would initiate a priceSettled
callback in the Insurance Arbitrator contract:
Based on the received callback parameters, this contract can identify the relevant claimId
that is used to get the stored insurance policy:
Importantly, all callbacks should be restricted to accept calls only from the Optimistic Oracle to avoid someone spoofing a resolved answer:
Depending on the resolved answer, this contract would either pay out the insured beneficiary and delete the insurance (in case of 1e18
representing the answer YES
, the insurance claim was valid) or reject the payout and re-open the policy for any subsequent claims:
Tests and deployment
All the unit tests covering the functionality described above are available here. To execute all of them, run:
Before deploying the contract check the comments on available environment variables in the deployment script.
In the case of the Görli testnet, the defaults would use the Finder instance that references the Mock Oracle implementation for resolving DVM requests. This exposes a pushPrice
method to be used for simulating a resolved answer in case of disputed proposals. Also, the default Görli deployment would use the already whitelisted TestnetERC20
currency that can be minted by anyone using its allocateTo
method.
To deploy the Insurance Arbitrator contract on Görli, run:
Optionally you can verify the deployed contract on Etherscan:
Interacting with deployed contract
The following section provide instructions on how to interact with the deployed contract from the Hardhat console, though one can also use it for guidance for interacting through another interface (e.g. Remix or Etherscan).
Start Hardhat console with:
Initial setup
From the Hardhat console, start by adding the required getAbi
dependency for interacting with UMA contracts and use the first two accounts as insurer and insured beneficiary:
Grab the deployed Insurance Arbitrator contract:
Issue insurance
Assuming TestnetERC20
was used as currency
when deploying, mint the required insurance amount (e.g. 10,000 TEST tokens) and approve the Insurance Arbitrator to pull them:
Issue the insurance policy and grab the resulting policyId
from the emitted PolicyIssued
event:
Submit insurance claim
First calculate the expected proposer bond:
Fetch the expected final fee from the Store
contract (which is discovered through the Finder
):
Calculate the expected total bond and provide funding/approval for the insured claimant:
Now initiate the insurance claim and grab request details from the RequestPrice
event emitted by the Optimistic Oracle:
Dispute insurance claim
Before liveness passes, the insurer can dispute the claim through the Optimistic Oracle. First, they must fund and approve with the same bonding amount:
If you are on a testnet like Göerli, in order to simulate UMA voting on a testnet, you can use the Mock Oracle:
Now initiate the dispute and grab the vote request details from the PriceRequestAdded
event emitted by the Mock Oracle:
Settle insurance claim
Before settling the claim, we can take a look at the vote request as seen by UMA voters:
The ancillaryData
should start with q:"Had the following insured event occurred as of request timestamp: Bad things have happened?"
. It is then followed by the ooRequester
key with our Insurance Arbitrator address in its value.
In order to simulate YES
as the resolved answer we would pass 1e18
as the price
parameter in the Mock Oracle pushPrice
method:
Now we can settle the request through the Optimistic Oracle and observe the emitted ClaimAccepted
from our Insurance Arbitrator contract:
The above settlement transaction should also transfer insuredAmount
tokens to the insured beneficiary as well as return the proposer bond to the claim initiator.
Alternatively, if 0
value was resolved, the settlement transaction should emit the ClaimRejected
event without paying out the insuredAmount
and returning the bond to the disputer, along with half of the proposer's bond.
Last updated