ETH Price: $1,944.59 (-1.88%)

Contract Diff Checker

Contract Name:
RewardProxy

Contract Source Code:

File 1 of 1 : RewardProxy

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

struct RecordReceipt {
    RecordKey record;
    Record old_record;
    Record new_record;
}

struct Record {
    uint64 height;
    uint48 timestamp;
    uint240 value;
}

struct RecordKey {
    uint8 systemid;
    uint64 cid;
    uint16 typ;
}

interface Oracle {
    function storeValuesWithReceipt(bytes memory dat) external payable returns (RecordReceipt[] memory receipts);
    function checkUpdatesWithReceipt(bytes memory dat) external view returns (RecordReceipt[] memory receipts);
}

contract RewardProxy { 

    event RewardsSimulated(
        address updater,
        uint8 system_id,
        uint64 chain_id,
        uint240 new_value,
        uint240 raw_deviation,
        uint48 time_since,
        uint256 time_reward, 
        uint256 deviation_reward,
        int256 reward_multiplier
    );

    event OracleUpdated(
        address updater,
        uint8 system_id,
        uint64 chain_id,
        uint240 new_value,
        uint240 raw_deviation,
        uint48 time_since,
        uint256 time_reward, 
        uint256 deviation_reward,
        int256 reward_multiplier
    );

    event RewardsToggled(
        bool rewards_on
    );

    event RewardsFreeze(
        bool rewards_frozen
    );

    struct TotalReward {
        address addr;
        uint256 total_rewards;
    }

    struct EnhancedReward {
        uint8 system_id;
        uint64 chain_id;
        uint64 height;
        uint240 gas_price;
        uint256 time_reward;
        uint256 deviation_reward;
    }

    struct ControlOutput {
        int80 kp;
        int80 ki;
        int80 co_bias;
    } 
    struct Coefficients {
        int96 zero;
        int96 one;
        int96 two;
        int96 three;
    } 
    struct RewardLimits {
        int256 min_time_reward;
        int256 max_time_reward;
        int256 min_deviation_reward;
        int256 max_deviation_reward;
    }

    int256 constant EIGHTEEN_DECIMAL_NUMBER = 10**18;
    int256 constant THIRTY_SIX_DECIMAL_NUMBER = 10**36;
    uint256 constant EIGHTEEN_DECIMAL_NUMBER_U = 10**18;

    uint256 constant EMA_ALPHA = 818181818181818176; // 1 - 2/11 
    uint256 constant ALPHA_COMP = EIGHTEEN_DECIMAL_NUMBER_U - EMA_ALPHA;


    mapping(uint72 => int256) private errorIntegral;
    mapping(uint72 => int256) private lastOutput; 

    uint32 private minWindowSize;
    
    mapping(address => uint256) private rewards;
    uint256 private totalRewards;
    mapping(uint72 => uint256) private insertedCount;   // Number of elements inserted so far, up to N
    mapping(uint72 => uint256) private scales;

    mapping(uint72 => uint256) private intervalEMAs;

    mapping(address => bool) private authorities;

    mapping(uint256 => address) private updaters;
    uint256 public updatersCount;

    bool private frozen;
    bool private rewardsEnabled;

    uint16 private rewardType;
 
    int256 minTimeReward;
    int256 maxTimeReward;
    int256 minDeviationReward;
    int256 maxDeviationReward;

    ControlOutput private controlOutput;

    int96 private coeffZero;
    int96 private coeffOne;
    int96 private coeffTwo;
    int96 private coeffThree;  

    int256 outputUpperBound;  
    int256 outputLowerBound;  
    uint256 targetTimeSince;
 
    Oracle private oracle;

    constructor(address o) {
        authorities[o] = true;
        frozen = true;
        rewardsEnabled = false;
    }

    function setOracle(address a) public {
        require(authorities[msg.sender], "Only owner can modify values");
        oracle = Oracle(a);
    }

    function setFreeze(bool f) public {
        require(authorities[msg.sender], "Only owner can modify values");
        frozen = f;
        emit RewardsFreeze(f);
    }

    function setRewardsProcess(bool f) public {
        require(authorities[msg.sender], "Only owner can modify values");
        rewardsEnabled = f;
        emit RewardsToggled(f);
    }

    function setRewardType(uint16 rType) public {  
        require(authorities[msg.sender], "Only owner can set reward type");
        rewardType = rType;
    }

    function updateScales(uint72[] calldata scid, uint256[] calldata  scale) public {
        require(authorities[msg.sender], "Only owner can set scales");
        require(scid.length == scale.length, "scid and scale length mismatch");
        for (uint256 i = 0; i < scid.length; ++i) {
            scales[scid[i]] = scale[i];
        }
    }

    function updateErrorIntegrals(uint72[] calldata scid, int256[] calldata errorIntegrals) public {
        require(authorities[msg.sender], "Only owner can set error integrals");
        require(scid.length == errorIntegrals.length, "scid and integrals length mismatch");
        for (uint256 i = 0; i < scid.length; ++i) {
            errorIntegral[scid[i]] = errorIntegrals[i];
        }
    }

    function getErrorIntegrals(uint72 scid) public view returns (int256) {
        return errorIntegral[scid];
    }

    function getLastOutput(uint72 scid) public view returns (int256) {
        return lastOutput[scid];
    }    

    function updateIntervalEMA(uint72[] calldata scid, uint256[] calldata  lIntervalEMAs) public {
        require(authorities[msg.sender], "Only owner can set interval EMAs");
        require(scid.length == lIntervalEMAs.length, "scid and emas length mismatch");
        for (uint256 i = 0; i < scid.length; ++i) {
            intervalEMAs[scid[i]] = lIntervalEMAs[i];
        }
    }

    function getFromIntervalEMAs(uint72 scid) public view returns (uint256) {
        return intervalEMAs[scid];
    }

    function setAuthority(address a, bool stat) public {  
        require(authorities[msg.sender], "Only owner can modify authorities"); 
        if (a != msg.sender) {
            require(stat, "Cannot revoke own access");
        }
       
        authorities[a] = stat;
    }

    function getTotalRewards() public view returns (uint256) {
        return totalRewards;
    }

    function getAddressRewards(address addr) public view returns (uint256) {
        return rewards[addr];
    }

    function getRewardType() public view returns (uint16) {
        return rewardType;
    }

    function getCoefficients() public view returns (Coefficients memory) {
        return Coefficients(coeffZero, coeffOne, coeffTwo, coeffThree);
    }

    function getScales(uint72 scid) public view returns (uint256) {
        return scales[scid];
    }

    function getGameParams() public view returns (
        ControlOutput memory,
        int256,
        int256,
        uint256,
        uint32,
        RewardLimits memory
    ) {
        return (
            controlOutput,
            outputUpperBound,
            outputLowerBound,
            targetTimeSince,
            minWindowSize,
            RewardLimits(minTimeReward, maxTimeReward, minDeviationReward, maxDeviationReward)
        );
    }


    function getOracleAddress() public view returns (address) {
        return address(oracle);
    }

    function setParams(
        ControlOutput calldata co, 
        int256 lOutputUpperBound,
        int256 lOutputLowerBound,
        uint256 lTargetTimeSince,
        RewardLimits calldata rl,
        uint32 lMinWindowSize
    ) public {
        require(authorities[msg.sender], "Only owner can set params");
        controlOutput = co;
        outputUpperBound = lOutputUpperBound;
        outputLowerBound = lOutputLowerBound;
        targetTimeSince = lTargetTimeSince;
        minWindowSize = lMinWindowSize;

        minTimeReward = rl.min_time_reward;
        maxTimeReward = rl.max_time_reward;
        minDeviationReward = rl.min_deviation_reward;
        maxDeviationReward = rl.max_deviation_reward;
    }

    function updateCoeffs(Coefficients calldata coeffs) public {
        require(authorities[msg.sender], "Only owner can update coefficients");
        coeffZero = coeffs.zero;
        coeffOne = coeffs.one;
        coeffTwo = coeffs.two;
        coeffThree = coeffs.three;
    }

    function initialSetParams(
        address lOracle,
        int256 lOutputUpperBound,
        int256 lOutputLowerBound,
        uint256 lTargetTimeSince,
        RewardLimits calldata rl,
        ControlOutput calldata co, 
        Coefficients calldata coeffs,
        uint32 lMinWindowSize,
        uint16 lRewardType
    ) public {
        setParams(
            co,
            lOutputUpperBound,
            lOutputLowerBound,
            lTargetTimeSince,
            rl,
            lMinWindowSize
        );
        setOracle(lOracle);
        setRewardType(lRewardType);
        updateCoeffs(coeffs);
    }

    // in case of a security breach, the owner can modify the reward values
    function ownerModifyReward(address ow, uint256 amount, uint256 tr) public {
        require(authorities[msg.sender], "Only owner can modify values");
        rewards[ow] = amount;
        totalRewards = tr;
    }

    function addUpdater(address updater) internal {
        require(updater != address(0), "Updater address is empty");
        if (rewards[updater] == 0) {
            updaters[updatersCount] = updater;
            updatersCount = updatersCount + 1;
        }
    }

    function scidToUint72(uint8 systemID, uint64 chainID) internal pure returns (uint72 scid) {
        assembly {
            scid := systemID
            scid := shl(0x40, scid)
            scid := add(scid, chainID)
        }
        return scid;
    }

    function submitUpdate(bytes memory dat) public payable returns ( EnhancedReward[] memory enhancedRewards) {
        require(!frozen, "Rewards contract is frozen");

        addUpdater(msg.sender);
        RecordReceipt[] memory receipts = oracle.storeValuesWithReceipt{value: msg.value}(dat);
        enhancedRewards = new EnhancedReward[](receipts.length);

        RecordReceipt memory rec;
        uint240 raw_deviation;
        uint256 l_total_rewards = 0;
        for (uint256 idx = 0; idx < receipts.length; idx++) {
            rec = receipts[idx];
            if (rec.record.typ != rewardType) {
                continue;
            }

            uint72 scid = scidToUint72(rec.record.systemid, rec.record.cid);
            uint256 deviation;
            {
                uint256 target_scale;
                target_scale = scales[scid]; 
                if (target_scale == 0) {
                    continue;
                }

                if (rec.new_record.height == 0) {
                    // emit event?
                    enhancedRewards[idx] = EnhancedReward(rec.record.systemid,rec.record.cid,rec.old_record.height,rec.old_record.value,0,0);
                    continue;
                }
                
                if (rec.new_record.value > rec.old_record.value ) {
                    raw_deviation = rec.new_record.value - rec.old_record.value;
                } else {
                    raw_deviation = rec.old_record.value - rec.new_record.value;
                }

                deviation = calcDeviation(target_scale, uint256(raw_deviation));
            }
            uint256 time_since = uint256(rec.new_record.timestamp - rec.old_record.timestamp) * EIGHTEEN_DECIMAL_NUMBER_U;

            //  calculate reward
            (int256 time_reward, int256 deviation_reward) = calcReward(int256(time_since)/1000, int256(deviation));

            int256 reward_mult = updateRewardMult(scid, insertedCount[scid], time_since/1000);
            insertedCount[scid] = insertedCount[scid] + 1;

            uint256 time_reward_adj = uint256(reward_mult * time_reward / EIGHTEEN_DECIMAL_NUMBER);
            uint256 deviation_reward_adj = uint256(reward_mult * deviation_reward / EIGHTEEN_DECIMAL_NUMBER);

             if (rewardsEnabled) {
                rewards[msg.sender] +=  time_reward_adj + deviation_reward_adj;
                emit OracleUpdated( msg.sender,
                        rec.record.systemid,
                        rec.record.cid,
                        rec.new_record.value, 
                        raw_deviation,
                        uint48(time_since/10**21),
                        time_reward_adj, 
                        deviation_reward_adj,
                        reward_mult);
                l_total_rewards += time_reward_adj + deviation_reward_adj;
            } else {
                emit RewardsSimulated(msg.sender,
                    rec.record.systemid,
                    rec.record.cid,
                    rec.new_record.value, 
                    raw_deviation,
                    uint48(time_since/10**21),
                    time_reward_adj, 
                    deviation_reward_adj,
                    reward_mult);
            }

            enhancedRewards[idx] = EnhancedReward(rec.record.systemid,rec.record.cid,rec.new_record.height,rec.new_record.value,time_reward_adj,deviation_reward_adj);
        }
        totalRewards += l_total_rewards;

        return enhancedRewards;
    }

 

function checkRewards(bytes memory dat) public view returns ( EnhancedReward[] memory enhancedRewards) {
        require(!frozen, "Rewards contract is frozen");
        RecordReceipt[] memory receipts = oracle.checkUpdatesWithReceipt(dat); 
        enhancedRewards = new EnhancedReward[](receipts.length);

        int256[] memory local_error_integrals = new int256[](receipts.length);
        uint256[] memory local_counts = new uint256[](receipts.length);
        uint256[] memory local_inverval_emas = new uint256[](receipts.length);

        for (uint256 idx = 0; idx < receipts.length; idx++) {
            RecordReceipt memory rec = receipts[idx];
            RecordKey memory recordkey = rec.record;
            if (recordkey.typ != rewardType) {
                continue;
            }

            if (scales[scidToUint72(recordkey.systemid, recordkey.cid)] == 0) {
                continue;
            }

            if (rec.new_record.height == 0) {
                enhancedRewards[idx] = EnhancedReward(recordkey.systemid,recordkey.cid,rec.old_record.height,rec.old_record.value,0,0);
                continue;
            }

            uint256 deviation;
            {    
                uint240 raw_deviation;
                    if (rec.new_record.value > rec.old_record.value ) {
                        raw_deviation = rec.new_record.value - rec.old_record.value;
                    } else {
                        raw_deviation = rec.old_record.value - rec.new_record.value;
                    }    
                deviation = calcDeviation(scales[scidToUint72(recordkey.systemid, recordkey.cid)], uint256(raw_deviation));
            }
            uint256 time_since = uint256(rec.new_record.timestamp - rec.old_record.timestamp) * EIGHTEEN_DECIMAL_NUMBER_U;
            int256 reward_mult;
            {
                (uint256 nidx, bool f) = checkPresence(receipts,  idx,  recordkey.systemid, recordkey.cid);
                uint256 _count;
                if (f) {
                    _count = local_counts[nidx]; 
                } else {
                    _count = insertedCount[scidToUint72(recordkey.systemid, recordkey.cid)]; 
                }

                if (f) {
                uint256 _inverval_ema  = getIntervalEMA(local_inverval_emas[nidx], time_since/1000);
                local_inverval_emas[nidx] = _inverval_ema;
            
                    if (_count + 1 < minWindowSize) {
                        reward_mult = EIGHTEEN_DECIMAL_NUMBER;
                    } else {
                        int256 u_err = calcError(int256(targetTimeSince), int256(_inverval_ema) );
                        int256 _error_integral  = local_error_integrals[nidx];
                        reward_mult = boundPiOutput(getRawPiOutput(u_err, _error_integral + u_err));
                        local_error_integrals[nidx] = clampErrorIntegral(reward_mult, _error_integral, _error_integral + u_err, u_err); 
                    }
                } else {
                    uint256 _inverval_ema  = getIntervalEMA(intervalEMAs[scidToUint72(recordkey.systemid, recordkey.cid)], time_since/1000);
                    local_inverval_emas[idx] = _inverval_ema;

                    if (_count + 1 < minWindowSize) {
                        reward_mult = EIGHTEEN_DECIMAL_NUMBER;
                    } else {
                        int256 u_err = calcError(int256(targetTimeSince), int256(_inverval_ema) );
                        int256 _error_integral  =  errorIntegral[scidToUint72(recordkey.systemid, recordkey.cid)]; 
                        reward_mult = boundPiOutput(getRawPiOutput(u_err, _error_integral + u_err));
                        local_error_integrals[idx] = clampErrorIntegral(reward_mult, _error_integral, _error_integral + u_err, u_err); 
                    }
                }
                
                if (f) {
                    local_counts[nidx] = _count + 1;
                } else {
                    local_counts[idx] = _count + 1;
                }
            }

            (int256 time_reward, int256 deviation_reward) = calcReward(int256(time_since)/1000, int256(deviation));
            enhancedRewards[idx] = EnhancedReward(recordkey.systemid,
                recordkey.cid,
                rec.old_record.height,
                rec.old_record.value,
                uint256(reward_mult * time_reward / EIGHTEEN_DECIMAL_NUMBER),
                uint256(reward_mult * deviation_reward / EIGHTEEN_DECIMAL_NUMBER));
        }

        return enhancedRewards;
    } 

    function checkPresence(RecordReceipt[] memory p, uint256 idx, uint8 systemid, uint64 chainid) private pure returns (uint256, bool) {
         for (uint256 i = 0; i < p.length; ++i) {
            if (systemid != 0 && chainid != 0 && p[i].record.systemid == systemid && p[i].record.cid == chainid) {
               if (i > idx) {
                    return (i, true);
                }
            }
        }
        return (0, false);
    }

    function max(int256 a, int256 b) private pure returns (int256) {
        return a >= b ? a : b;
    }

    function min(int256 a, int256 b) private pure returns (int256) {
        return a < b ? a : b;
    }

    function boundPiOutput(int256 piOutput) private view returns (int256 boundedPiOutput) {
        if (piOutput < outputLowerBound) {
            return outputLowerBound;
        } else if (piOutput > outputUpperBound) {
            return outputUpperBound;
        }
        return piOutput;
    }

    function clampErrorIntegral(int256 boundedPiOutput, int256 lErrorIntegral, int256 newErrorIntegral, int256 newArea) private view returns (int256) {
        // This logic is strictly for a *reverse-acting* controller where controller
        // output is opposite sign of error(kp and ki < 0)
        if (boundedPiOutput == outputLowerBound && newArea > 0 && lErrorIntegral > 0) {
            return newErrorIntegral - newArea;
        } else if (boundedPiOutput == outputUpperBound && newArea < 0 && lErrorIntegral < 0) {
            return newErrorIntegral - newArea;
        }
        return newErrorIntegral;
    }

    function getRawPiOutput(int256 err, int256 errI ) private view returns (int256 piOutput) {
        // output = P + I = Kp * error + Ki * errorI 
        int256 p_output = (err * int256(controlOutput.kp)) / EIGHTEEN_DECIMAL_NUMBER;
        int256 i_output = (errI * int256(controlOutput.ki)) / EIGHTEEN_DECIMAL_NUMBER;
        return int256(controlOutput.co_bias) + p_output + i_output;
    }

    function calcError(int256 target, int256 measured) private pure returns (int256) {
        return (target - measured) * EIGHTEEN_DECIMAL_NUMBER / target;
    }

    function calcDeviation(uint256 targetScale, uint256 valueDiff) private pure returns (uint256) {
        return valueDiff*EIGHTEEN_DECIMAL_NUMBER_U/targetScale;
    }

    function updateRewardMult(uint72 scid, uint256 lCount, uint256 timeSince) private returns (int256 rewardMult) {
        // Update oracle update_interval
        uint256 _inverval_ema = getIntervalEMA(intervalEMAs[scid], timeSince);
        intervalEMAs[scid] = _inverval_ema;

        // Dont use feedback if number of samples is lt window size
        if (lCount + 1 < minWindowSize) {
            return EIGHTEEN_DECIMAL_NUMBER;
        }
        //  update feedback mechanism and get current reward multiplier
        return updateFeedback(scid, calcError(int256(targetTimeSince), int256(_inverval_ema) ));
    }
  
    function calcTimeReward(int256 timeSince) private view returns (int256) {       
        return max(
                min(    
                    int256(coeffZero)*timeSince/EIGHTEEN_DECIMAL_NUMBER + 
                    int256(coeffTwo)*timeSince*timeSince/THIRTY_SIX_DECIMAL_NUMBER, 
                    maxTimeReward), 
                minTimeReward);
    }

    function calcDeviationReward(int256 deviation) private view returns (int256) {
        return max(
                min(
                    int256(coeffOne)*deviation/EIGHTEEN_DECIMAL_NUMBER +
                    int256(coeffThree)*deviation*deviation/THIRTY_SIX_DECIMAL_NUMBER,
                    maxDeviationReward),
                minDeviationReward);
    } 

    function calcReward(int256 timeSince, int256 deviation) private view returns (int256, int256) {
        return (calcTimeReward(timeSince), calcDeviationReward(deviation));
    }

    function getIntervalEMA(uint256 lIntervalEMA, uint256 newValue) private pure returns (uint256) {
        return (ALPHA_COMP * newValue + EMA_ALPHA * lIntervalEMA) / EIGHTEEN_DECIMAL_NUMBER_U;
    }
    
    function updateFeedback(uint72 scid, int256 err ) private returns (int256 boundedPiOutput) {
        // update feedback mechanism
        int256 _error_integral = errorIntegral[scid];
        int256 _new_error_integral = _error_integral + err;

        boundedPiOutput = boundPiOutput(getRawPiOutput(err, _new_error_integral));

        errorIntegral[scid] = clampErrorIntegral(boundedPiOutput, _error_integral, _new_error_integral, err);
        lastOutput[scid] = boundedPiOutput;

        return boundedPiOutput; 
    }

    function getUpdatersChunk(uint256 start, uint256 count) public view returns (TotalReward[] memory rewardsChunk) { 
        rewardsChunk = new TotalReward[](count);
        for (uint256 i = 0; i < count; ++i) { 
            address updater = updaters[start + i];
            if (updater == address(0)) {
                break;
            }
            rewardsChunk[i] = TotalReward(updater, rewards[updater]);
        }

        return rewardsChunk;
    }

}

Please enter a contract address above to load the contract details and source code.

Context size (optional):