-
Notifications
You must be signed in to change notification settings - Fork 2
feat: StreamWeightActor #17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 15 commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
133d576
feat: add StreamWeightActor with unanimousNoHold governance and tests
wjmelements c9c4809
fix: support metadata-stripped SafeProxy
wjmelements b29d163
Merge remote-tracking branch 'origin/main' into swa
wjmelements 8c3eff9
fix: change Epoch to uint64
wjmelements 6be758b
feat: GateParams, quarterlyGateCheck
wjmelements 4fbacf5
feat: use SRA.qEnd
wjmelements 16413bf
chore: forge fmt
wjmelements 6b2e4a2
feat: GateParamsLibrary.init
wjmelements d4ea2d7
test: FixedU18exp
wjmelements 284f8fb
chore: forge fmt
wjmelements e577a00
perf: simplify exp
wjmelements d8ea8ee
perf: assembly FixedU18.exp
wjmelements b12f80f
fix(swa): skip StepWeightRecords write when the gate fails
wjmelements b95a375
refactor(swa): read QUARTER and HOLD from the SRA
wjmelements b5050a8
perf(swa): skip the unused final squaring in FixedU18.exp
wjmelements 7dd29d3
docs(swa): add natspec to public methods
wjmelements b4596d7
fix: measured >= threshold
wjmelements 78e0e26
fix: step not slope
wjmelements 8f52244
fix: remove lastCheckedQuarter from GateParams
wjmelements File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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
This file contains hidden or 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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,128 @@ | ||
| // SPDX-License-Identifier: Apache-2.0 OR MIT | ||
| pragma solidity ^0.8.36; | ||
|
|
||
| import {IServiceRewardsActor} from "./interfaces/IServiceRewardsActor.sol"; | ||
| import {Epoch} from "./lib/Epoch.sol"; | ||
| import {FixedU18} from "./lib/FixedU18.sol"; | ||
| import {GateParams, GateParamsLibrary} from "./lib/GateParams.sol"; | ||
| import {FVMRewards} from "./lib/FVMRewards.sol"; | ||
| import {PendingOp, Share, WeightRecord, WeightRecordUpdate} from "./lib/FVMRewardTypes.sol"; | ||
| import {OwnersLibrary} from "./lib/Owners.sol"; | ||
| import {UnanimousGovernance} from "./lib/UnanimousGovernance.sol"; | ||
| import {IsASafe} from "./lib/IsASafe.sol"; | ||
|
|
||
| uint64 constant SERVICE_ID = 2; | ||
|
|
||
| int256 constant INITIAL = 5e16; // 5% | ||
| int256 constant STEP = 5e16; // 5% | ||
|
|
||
| contract StreamWeightActor is UnanimousGovernance { | ||
| using IsASafe for address; | ||
| using OwnersLibrary for address; | ||
|
|
||
| IServiceRewardsActor immutable SRA; | ||
| Epoch immutable QUARTER; | ||
| Epoch immutable HOLD; | ||
|
|
||
| constructor(address owner1, address owner2, IServiceRewardsActor sra) { | ||
| owner1.isProbablyASafe(); | ||
| owner2.isProbablyASafe(); | ||
|
|
||
| owner1.addOwner(); | ||
| owner2.addOwner(); | ||
|
|
||
| SRA = sra; | ||
| QUARTER = sra.EPOCHS_PER_QUARTER(); | ||
| HOLD = sra.SRA_CANCEL_HOLD(); | ||
|
|
||
| GateParamsLibrary.init(); | ||
| } | ||
|
|
||
| function registerStream(uint64 id, WeightRecord calldata record, uint64 activationEpoch) | ||
| external | ||
| unanimousNoHold(keccak256(msg.data)) | ||
| { | ||
| // hold enforced in f02 | ||
| FVMRewards.registerStream(id, record, activationEpoch); | ||
| } | ||
|
|
||
| function registerStream( | ||
| uint64 id, | ||
| WeightRecord calldata record, | ||
| address writer, | ||
| Share[] calldata shares, | ||
| uint64 activationEpoch | ||
| ) external unanimousNoHold(keccak256(msg.data)) { | ||
| // hold enforced in f02 | ||
| FVMRewards.registerStream(id, record, writer, shares, activationEpoch); | ||
| } | ||
|
|
||
| function removeStream(uint64 id) external unanimousNoHold(keccak256(msg.data)) { | ||
| // hold enforced in f02 | ||
| FVMRewards.removeStream(id); | ||
| } | ||
|
|
||
| function setWeightRecords(WeightRecordUpdate[] calldata updates) external unanimousNoHold(keccak256(msg.data)) { | ||
| // hold enforced in f02 | ||
| FVMRewards.setWeightRecords(updates); | ||
| } | ||
|
|
||
| function setDistribution(uint64 id, address writer) external unanimousNoHold(keccak256(msg.data)) { | ||
| // hold enforced in f02 | ||
| FVMRewards.setDistribution(id, writer); | ||
| } | ||
|
|
||
| function cancelPending(uint64 id, PendingOp op) external { | ||
| // any owner can immediately cancel any pending operation | ||
| require(msg.sender.isOwner()); | ||
| FVMRewards.cancelPending(id, op); | ||
| } | ||
|
|
||
| function cancelPendingWeight(PendingOp op) external { | ||
| // any owner can immediately cancel any pending operation | ||
| require(msg.sender.isOwner()); | ||
| FVMRewards.cancelPendingWeight(op); | ||
| } | ||
|
|
||
| function replaceOwner(address prevOwner, address newOwner) external unanimousNoHold(keccak256(msg.data)) { | ||
| newOwner.isProbablyASafe(); | ||
| prevOwner.removeOwner(); | ||
| newOwner.addOwner(); | ||
| } | ||
|
|
||
| error StepsComplete(); | ||
|
|
||
| function quarterlyGateCheck() external { | ||
| GateParamsLibrary.GateParamsInfo storage gateParamsInfo = GateParamsLibrary.getGateParamsSlot(); | ||
| GateParams memory loaded = gateParamsInfo.params; | ||
| require(loaded.steps < 8, StepsComplete()); | ||
|
|
||
| uint64 quarter = loaded.lastCheckedQuarter + 1; | ||
| // NOTE this will enforce afterBinding() | ||
|
wjmelements marked this conversation as resolved.
|
||
| FixedU18 fpv = SRA.aggregatedFPV(quarter); | ||
|
|
||
| if (fpv > loaded.nextThreshold()) { | ||
|
wjmelements marked this conversation as resolved.
Outdated
|
||
| int256 start = (int256(uint256(loaded.steps)) + 2) * STEP; | ||
|
|
||
| WeightRecordUpdate[] memory updates = new WeightRecordUpdate[](1); | ||
| updates[0].id = SERVICE_ID; | ||
| updates[0].record.floor = INITIAL; | ||
| updates[0].record.tStart = SRA.qEnd(quarter); | ||
| updates[0].record.vStart = start; | ||
| updates[0].record.cap = start + STEP; | ||
| updates[0].record.slope = | ||
| (updates[0].record.cap - updates[0].record.vStart) / int256(uint256(Epoch.unwrap(QUARTER))); | ||
|
wjmelements marked this conversation as resolved.
Outdated
|
||
|
|
||
| loaded.steps++; | ||
| loaded.lastCheckedQuarter = quarter; | ||
| gateParamsInfo.params = loaded; | ||
| FVMRewards.stepWeightRecords(updates); | ||
| } else { | ||
| gateParamsInfo.params.lastCheckedQuarter = quarter; | ||
| } | ||
| } | ||
|
|
||
| function setGateParams(GateParams calldata params) external unanimous(keccak256(msg.data), HOLD) { | ||
| GateParamsLibrary.getGateParamsSlot().params = params; | ||
| } | ||
| } | ||
This file contains hidden or 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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| // SPDX-License-Identifier: Apache-2.0 OR MIT | ||
| pragma solidity ^0.8.36; | ||
|
|
||
| import {Epoch} from "../lib/Epoch.sol"; | ||
| import {FixedU18} from "../lib/FixedU18.sol"; | ||
|
|
||
| interface IServiceRewardsActor { | ||
| function aggregatedFPV(uint64 quarter) external view returns (FixedU18 filecoinPayVolume); | ||
| function qEnd(uint64 quarter) external view returns (Epoch quarterEnd); | ||
| function EPOCHS_PER_QUARTER() external view returns (Epoch oneQuarter); | ||
| function SRA_CANCEL_HOLD() external view returns (Epoch hold); | ||
| } |
This file contains hidden or 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
This file contains hidden or 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
This file contains hidden or 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
This file contains hidden or 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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| // SPDX-License-Identifier: Apache-2.0 OR MIT | ||
| pragma solidity ^0.8.36; | ||
|
|
||
| type FixedU18 is uint256; | ||
|
|
||
| using { | ||
| unsafeAdd as +, | ||
| unsafeSub as -, | ||
| unsafeMulDown as *, | ||
| divDown as /, | ||
| equals as ==, | ||
| greaterThan as >, | ||
| lessThan as <, | ||
| greaterThanOrEqualTo as >=, | ||
| lessThanOrEqualTo as <= | ||
| } for FixedU18 global; | ||
|
|
||
| using FixedU18Library for FixedU18 global; | ||
|
|
||
| uint256 constant ONE_WAD = 1 ether; | ||
| FixedU18 constant ONE = FixedU18.wrap(ONE_WAD); | ||
|
|
||
| // type(uint256).max / ONE_WAD | ||
| uint256 constant MAX_DIVIDEND_WAD = 115792089237316195423570985008687907853269984665640564039457; | ||
| FixedU18 constant MAX_DIVIDEND = FixedU18.wrap(MAX_DIVIDEND_WAD); | ||
|
|
||
| error DividendTooLarge(FixedU18 dividend); | ||
| bytes4 constant DIVIDEND_TOO_LARGE_SELECTOR = 0x7b5479ff; | ||
|
|
||
| // @dev returns zero if divisor is zero | ||
| function divDown(FixedU18 dividend, FixedU18 divisor) pure returns (FixedU18 quotient) { | ||
| assembly ("memory-safe") { | ||
| if gt(dividend, MAX_DIVIDEND_WAD) { | ||
| mstore(0, DIVIDEND_TOO_LARGE_SELECTOR) | ||
| mstore(32, dividend) | ||
| revert(28, 36) | ||
| } | ||
| quotient := div(mul(dividend, ONE_WAD), divisor) | ||
| } | ||
| } | ||
|
|
||
| // @dev overflows if sum would be greater than 2**256/10**18 (approximatly 10**59) | ||
|
wjmelements marked this conversation as resolved.
|
||
| function unsafeAdd(FixedU18 addend1, FixedU18 addend2) pure returns (FixedU18 sum) { | ||
| assembly ("memory-safe") { | ||
| sum := add(addend1, addend2) | ||
| } | ||
| } | ||
|
|
||
| // @dev underflows if subtrahend is greater than minuend | ||
| function unsafeSub(FixedU18 minuend, FixedU18 subtrahend) pure returns (FixedU18 difference) { | ||
| assembly ("memory-safe") { | ||
| difference := sub(minuend, subtrahend) | ||
| } | ||
| } | ||
|
|
||
| // @dev overflows if product would be greater than 2**256/10**18 (approximatly 10**59) | ||
| function unsafeMulDown(FixedU18 factor1, FixedU18 factor2) pure returns (FixedU18 product) { | ||
| assembly ("memory-safe") { | ||
| product := div(mul(factor1, factor2), ONE_WAD) | ||
| } | ||
| } | ||
|
|
||
| function equals(FixedU18 a, FixedU18 b) pure returns (bool) { | ||
| return FixedU18.unwrap(a) == FixedU18.unwrap(b); | ||
| } | ||
|
|
||
| function greaterThan(FixedU18 a, FixedU18 b) pure returns (bool) { | ||
| return FixedU18.unwrap(a) > FixedU18.unwrap(b); | ||
| } | ||
|
|
||
| function lessThan(FixedU18 a, FixedU18 b) pure returns (bool) { | ||
| return FixedU18.unwrap(a) < FixedU18.unwrap(b); | ||
| } | ||
|
|
||
| function greaterThanOrEqualTo(FixedU18 a, FixedU18 b) pure returns (bool) { | ||
| return FixedU18.unwrap(a) >= FixedU18.unwrap(b); | ||
| } | ||
|
|
||
| function lessThanOrEqualTo(FixedU18 a, FixedU18 b) pure returns (bool) { | ||
| return FixedU18.unwrap(a) <= FixedU18.unwrap(b); | ||
| } | ||
|
|
||
| library FixedU18Library { | ||
| // @dev overflows if product would be greater than 2**256/10**18 (approximatly 10**59) | ||
| function mul(FixedU18 factor1, uint256 factor2) internal pure returns (FixedU18 product) { | ||
| assembly ("memory-safe") { | ||
| product := mul(factor1, factor2) | ||
| } | ||
| } | ||
|
|
||
| function exp(FixedU18 base, uint64 exponent) internal pure returns (FixedU18 power) { | ||
| assembly ("memory-safe") { | ||
| power := ONE_WAD | ||
| if exponent { | ||
| for {} gt(exponent, 1) {} { | ||
| if and(1, exponent) { | ||
| power := div(mul(base, power), ONE_WAD) | ||
| } | ||
| base := div(mul(base, base), ONE_WAD) | ||
| exponent := shr(1, exponent) | ||
| } | ||
| power := div(mul(base, power), ONE_WAD) | ||
| } | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or 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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| // SPDX-License-Identifier: Apache-2.0 OR MIT | ||
| pragma solidity ^0.8.36; | ||
|
|
||
| import {FixedU18} from "./FixedU18.sol"; | ||
|
|
||
| FixedU18 constant VOL_TARGET_ENTRY = FixedU18.wrap(3500 ether); | ||
| FixedU18 constant VOL_TARGET_RATIO = FixedU18.wrap(2.7 ether); | ||
|
|
||
| struct VolumeTarget { | ||
| FixedU18 base; | ||
| FixedU18 stepRatio; | ||
| } | ||
|
|
||
| struct GateParams { | ||
| VolumeTarget target; | ||
| uint64 steps; | ||
| uint64 lastCheckedQuarter; | ||
|
wjmelements marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| using GateParamsLibrary for GateParams global; | ||
|
|
||
| library GateParamsLibrary { | ||
| /// @custom:storage-location erc7201:Solstice.GateParams | ||
| struct GateParamsInfo { | ||
| GateParams params; | ||
| } | ||
|
|
||
| // keccak256(abi.encode(uint256(keccak256("Solstice.GateParams")) - 1)) & ~bytes32(uint256(0xff)); | ||
| bytes32 private constant GATE_PARAMS_SLOT = 0xf9abab00248d945495524c8caf6be2b837274c1becd1964fb3775f62fd6e4600; | ||
|
|
||
| function getGateParamsSlot() internal pure returns (GateParamsInfo storage slot) { | ||
| assembly ("memory-safe") { | ||
| slot.slot := GATE_PARAMS_SLOT | ||
| } | ||
| } | ||
|
|
||
| function nextThreshold(GateParams memory params) internal pure returns (FixedU18 fpvThreshold) { | ||
| return params.target.base * params.target.stepRatio.exp(params.steps); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. needs a floor here
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We'll just remove the floor from the spec. |
||
| } | ||
|
|
||
| function init() internal { | ||
| GateParams memory params; | ||
| params.target.base = VOL_TARGET_ENTRY; | ||
| params.target.stepRatio = VOL_TARGET_RATIO; | ||
| params.lastCheckedQuarter = 1; | ||
| GateParamsLibrary.getGateParamsSlot().params = params; | ||
| } | ||
| } | ||
This file contains hidden or 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
This file contains hidden or 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
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.