How can I automate Smart Contract code execution?

571 views Asked by At

I have 2 contracts: PolicyFactory (a contract that constructs Policy contracts) and Policy.

In my React app any user can apply for a policy (i have a descentralized life insurance app), resulting in the creation of a Policy contract for each policy.

I want to implement a logic that close the policy and send the funds to the owner automatically when the end date of the policy is due (i store the end date as uint256 in the smart contract).

I tried this with Chainlink Keeper but i found it impossible since i need a keeper for every new Policy contract that is created...

How can i automate this for every policy my users have?

These are my contracts:

import "https://github.com/smartcontractkit/chainlink/blob/develop/contracts/src/v0.7/KeeperCompatible.sol";

contract PolicyFactory {
    //address[] public deployedPolicies;
    mapping(address => address[]) private deployedPoliciesByUser;
    address[] private deployedPolicies;
    address private admin = 0x3402c11c6f40e28b1D3996f11E5e54a937161fb9;

    event PolicyCreation(address policyAddress, string applicationId);

    modifier restricted(){
        require(msg.sender == admin, 'Not admin!');
        _;
    }

    function createPolicy(uint timePeriod, uint premium, address owner, uint startDate, string memory applicationId, uint endDate) public restricted returns (address){
       Policy newPolicy = new Policy(owner, premium, timePeriod, startDate, applicationId, endDate);
       deployedPoliciesByUser[owner].push(address(newPolicy));
       deployedPolicies.push(address(newPolicy));
       emit PolicyCreation(address(newPolicy), applicationId);    
    }

    function getDeployedPoliciesByUser(address user) public view returns (address[] memory)  {
        require(user != address(0));
        return deployedPoliciesByUser[user];
    }

    function getDeployedPolicies() public view restricted returns (address[] memory) { 
        return deployedPolicies;
    }
}

contract Policy is KeeperCompatibleInterface {
    address public owner;
    address public admin = 0x3402c11c6f40e28b1D3996f11E5e54a937161fb9;
    string public applicationId;
    uint public timePeriod;
    uint public premium;
    uint public startDate;
    uint public endDate;
    uint public nrPremiumsPayed;
    bool public active = true;

    event LogDeposit(address _from, uint amount, uint date);
    event LogWithdraw(address _to, uint amount, uint date);


    modifier restrictedAdminAndOwner(){
        require(address(msg.sender) == admin || address(msg.sender) == owner);
        _;
    }

    constructor(address _owner, uint _premium, uint _period, uint _startDate, string memory _applicationId, uint _endDate) public {
       owner = _owner;
       timePeriod = _period;
       premium = _premium;
       startDate = _startDate;
       endDate = _endDate;
       applicationId = _applicationId;
   }

   function checkUpkeep(bytes calldata /* checkData */) external view override returns (bool upkeepNeeded, bytes memory /* performData */) {
        upkeepNeeded = (endDate - block.timestamp) <= 100000;
    }

    function performUpkeep(bytes calldata /* performData */) external override {
        if((endDate - block.timestamp) <= 100000) {
            active = false;
            (bool sent, ) = address(owner).call{value: address(this).balance}("");
            require(sent);
        }
    }

    // de intregrat cu oracle de ipfs sa trimita automat cand se gaseste document.
   function sendCompensation () public restrictedAdminAndOwner {
       require(owner != address(0) && active == false);
        (bool sent, ) = address(owner).call{value: address(this).balance}("");
        require(sent);
   }

   function getSummary () public view returns(address, address, uint, uint, uint, bool, uint, uint){
       return (
           owner, admin, timePeriod,premium,startDate,active,address(this).balance, endDate
       );
   }

    function withdraw (uint amount, bool closePolicy) public {
        require(msg.sender == owner && amount <= address(this).balance && active == true);
        (bool sent, ) = address(owner).call{value: amount}("");
        require(sent);
        if(closePolicy == true){
            active = false;
            sendCompensation();
        }
        emit LogWithdraw(address(owner), amount, block.timestamp);
    }

    function deposit () public payable {
        require(active == true);
        emit LogDeposit(address(msg.sender), msg.value, block.timestamp);
    } 
}

4

There are 4 answers

0
Crazy Punch Man On

Let the contract execute automatically on blockchain is impossible, but there are some alternative solution.

Some simple ideas

  1. You can use ethers.js or web3.js libary to interact with blockchain. But be aware, if you want to change any data on blockchain. You will spend a certain amount of gas fee(like automatically send the funds to the owner).

  2. You can create a dapp ,when the time up, the dapp will show people that they can claim there own fund.(Because the executer is people who claim.)

If I have any new idea I will update ASAF. :)

0
Jacopo Mosconi On

You can make a cron job that checks the endDate for every users, if the date is passed it will send a transaction to withdraw funds to the owner

or as Crazy Punch Man said, this is a thing that people can do make a require inside the withdraw() function like:

require(now >= endDate, "you can't withdraw");

then make the dapp will show the withdraw button only if enough time is passed, and people will withdraw by their own

0
Abi Ji On

First of all, Ethereum, by design does not offer any kind of 'automated execution' like cronjob in linux or tasks in windows. every transaction (i.e execution) must start with an EOA (Externally Owned Account) . So there are couple of solution you can work around them to fulfill your requirement

  1. Using Oracle services, like Chainlink keepers as per @RoundAboutPI answer and the given instruction.

  2. Using a web3 script on your local environment and execute it by your OS provided tools of automation. i.e Cronjob in Linux and Tasks on windows. That requires you to set Ownable functionality in your contract and It's less likely a decenteralized solution.

  3. Define a modifier based on your endDate to revert() execution of specific tasks (Which shouldn't be functional on post-expiration) and design a destroy() function to be callable by any EOA and send contract remaining ether to the specified address.

1
RoundAboutPi On

You can use Chainlink Keepers to automate this smart contract function in the following ways.

Option one: Separate Upkeep Contract that loops through all "live" policies

Create a separate Keeper-Compatible End-Of-Life contract. It will call getDeployedPolicies() on your factory and then iterate over each policy in checkUpkeep to detect which ones should be closed out. You then build a new list of policies that need to be expired, abi encode and pass as performData into the performUpkeep function. In performUpkeep you abi decode and then revalidate that they do need to be closed out. I'd also suggest that the balance_return function you call should check that the contract is still eligible to close before returning funds.This is similar to how some users implement limit orders today.

A constraint of this approach is that you can only do 6.5M gas worth of calculation in checkUpkeep at the moment. Users get around this by using the same function plus a checkData input to split the lists. This way you can use the same upkeep contract, just pass in different checkData to check different parts of the list of contracts.

Note, I'm not sure if getDeployedPolicies will return all policies, or just policies that have not been closed out. Side question: Where do you trigger the policy before expiration?

I'll get time to add pseudo code here.

Option two: Automatically create Upkeep as part of newPolicy creation by directly interacting with the Keepers Registry

For each policy that you deploy, automate the deployment of Chainlink Keeper Upkeep as part of your factory contract. This is perhaps a cleaner solution, but more complex at the moment. Given the manual approval process involved today, you need some follow up logic to fetch the final ID of the Upkeep once it has been approved. Note we are doing some work in this regard which should make it easier to do in future.

Something else that might be useful here is just the ability to call close-out on a specific date and at specific time, without all this checking.

Disclaimer: I work for Chainlink Labs