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;
}
}