Skip to content
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

Fix audit and invariants #62

Merged
merged 122 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
122 commits
Select commit Hold shift + click to select a range
ab6e95c
feat: test that shows bug in total voting power
GalloDaSballo Oct 28, 2024
92a5b07
feat: minimal extra test to check `DISABLED` math accounting
GalloDaSballo Oct 28, 2024
429f02f
fix: incorrect accounting from DISABLED initiatives
GalloDaSballo Oct 28, 2024
3a529e6
chore: cleanup
GalloDaSballo Oct 28, 2024
28ee9fe
polish: explicit non negative checks
GalloDaSballo Oct 28, 2024
8c35064
chore: audit tags
GalloDaSballo Oct 28, 2024
6700b41
Merge branch 'dev' of github.com:liquity/V2-gov into dev-fix-properties
GalloDaSballo Oct 28, 2024
7859f62
fix: use calldata to check dups
GalloDaSballo Oct 28, 2024
c8b5ec3
fix: use `_encodeLQTYAllocation`
GalloDaSballo Oct 28, 2024
8865787
fix: edge case of remainer
GalloDaSballo Oct 28, 2024
d5838a8
feat: use remainer and amount
GalloDaSballo Oct 28, 2024
9ad09cf
chore: rename _deposit
GalloDaSballo Oct 29, 2024
8e6a645
chore: cleanup
GalloDaSballo Oct 29, 2024
0266514
feat: stateful test for reset soundness
GalloDaSballo Oct 29, 2024
aff1bef
chore: undo yolo reset change
GalloDaSballo Oct 29, 2024
4a73edc
feat: can be reprod manually
GalloDaSballo Oct 29, 2024
432e88c
chore: repro
GalloDaSballo Oct 29, 2024
5a82349
chore: future notes on V2 Gauge
GalloDaSballo Oct 29, 2024
696cfde
chore: make tests compile
GalloDaSballo Oct 29, 2024
66a29a0
feat: aded reset alloc calls
GalloDaSballo Oct 29, 2024
5cfeff0
feat: new property for reset
GalloDaSballo Oct 29, 2024
b2972be
chore: comments
GalloDaSballo Oct 29, 2024
5ac7754
Merge branch 'cs-prelim-fixes' into fix-audit-and-invariants
GalloDaSballo Oct 29, 2024
2f59274
Merge branch 'exp-reset-all' into fix-audit-and-invariants
GalloDaSballo Oct 29, 2024
81f3e55
chore: comment
GalloDaSballo Oct 29, 2024
a4a40bf
chore: spaces
GalloDaSballo Oct 30, 2024
640208d
feat: registerable test
GalloDaSballo Oct 30, 2024
11d65f4
fix: governance UNREGISTER fsm
GalloDaSballo Oct 30, 2024
cb0c95a
feat: allocation reset check is optional
GalloDaSballo Oct 30, 2024
14e147e
chore: remove old repro
GalloDaSballo Oct 30, 2024
626f507
chore: clean up shadowing
GalloDaSballo Oct 30, 2024
f9628e8
chore: cleanuo
GalloDaSballo Oct 30, 2024
d3ff4af
Merge pull request #64 from liquity/fix-dd-review
GalloDaSballo Oct 30, 2024
78688ab
chore: cleanup
GalloDaSballo Oct 30, 2024
ef8bd64
feat: alignment
GalloDaSballo Oct 31, 2024
afc0eea
feat: repro of insolvency property
GalloDaSballo Oct 31, 2024
14a5030
chore: compilation
GalloDaSballo Oct 31, 2024
2a27ce6
chore: repro
GalloDaSballo Oct 31, 2024
79cc29f
fix: property specification ignored DISABLED initiatives
GalloDaSballo Oct 31, 2024
0b899e8
fix: needless empty values
GalloDaSballo Oct 31, 2024
998cd9f
chore: remove shadowing
GalloDaSballo Oct 31, 2024
2378fcc
feat: debug BI07
GalloDaSballo Oct 31, 2024
d622bd5
feat: additional optimization tests
GalloDaSballo Oct 31, 2024
cca360b
chore: opt
GalloDaSballo Oct 31, 2024
d8f97ce
chore: default to optimization
GalloDaSballo Oct 31, 2024
d29f57c
fix: BribeInitiativeAllocate tests
nican0r Oct 31, 2024
c931d57
Merge branch 'fix-temp-invariant-align-bribes' of https://github.com/…
nican0r Oct 31, 2024
3f7705a
feat: revert properties for all tests
GalloDaSballo Oct 31, 2024
33ece7f
Merge pull request #67 from liquity/fix-temp-invariant-align-bribes
GalloDaSballo Oct 31, 2024
e1df25c
chore: cleanup
GalloDaSballo Oct 31, 2024
b18c629
chore: e2E
GalloDaSballo Oct 31, 2024
f693d5f
fix: cap amount for bribes
GalloDaSballo Oct 31, 2024
01457bd
chore: desc
GalloDaSballo Oct 31, 2024
a589a29
feat: upscale ts to u120
GalloDaSballo Nov 1, 2024
7e777ae
feat: tests somewhat compile
GalloDaSballo Nov 1, 2024
d27487f
feat: invariants compile and have a few TS tests
GalloDaSballo Nov 1, 2024
5d69f9a
chore: easy tests are fixed
GalloDaSballo Nov 1, 2024
4b75ce1
chore: fix mock tests
GalloDaSballo Nov 1, 2024
7c8a577
fix: epochStart is magnified with a hacky solution
GalloDaSballo Nov 1, 2024
105bc32
fix: test ts are magnified
GalloDaSballo Nov 1, 2024
075bf4b
chore: fix overflow
GalloDaSballo Nov 1, 2024
dc845b3
fix: abi decode
GalloDaSballo Nov 1, 2024
28fe9d9
fix: BI-07
GalloDaSballo Nov 1, 2024
56d8e86
WARNING: Add tollerance
GalloDaSballo Nov 1, 2024
d82a0c3
feat: laxer property
GalloDaSballo Nov 1, 2024
6482f51
feat: debug and fix properties
GalloDaSballo Nov 1, 2024
4db439e
fix: BI07
GalloDaSballo Nov 1, 2024
723e9e1
feat: truncation but maybe not so bad
GalloDaSballo Nov 1, 2024
90af7db
fix: we need a higher confidence range
GalloDaSballo Nov 1, 2024
d559696
feat: raise the precision to 1e26
GalloDaSballo Nov 1, 2024
81c048e
fix: upscale the precision in bribes
GalloDaSballo Nov 1, 2024
50933d0
chore: fix tests
GalloDaSballo Nov 1, 2024
bd2cc0a
feat: trying out overflow mode
GalloDaSballo Nov 1, 2024
0572a61
chore: cleanup
GalloDaSballo Nov 1, 2024
9259e26
feat: document overflow risks
GalloDaSballo Nov 1, 2024
ceffe66
fix: overflow in timestamp math
GalloDaSballo Nov 1, 2024
426de19
chore: type consistency
GalloDaSballo Nov 1, 2024
3ecb7a2
fix: ensure user TS never decreases
GalloDaSballo Nov 1, 2024
26d4414
chore: comments on overflow risk
GalloDaSballo Nov 1, 2024
55b94aa
gas: order of operations
GalloDaSballo Nov 1, 2024
9548318
chore: rename fn
GalloDaSballo Nov 1, 2024
0246b32
chore: order of operations for gas
GalloDaSballo Nov 1, 2024
3205922
chore: consistent types
GalloDaSballo Nov 1, 2024
84fcb22
chore: added precision invariant on `getInitiativeState`
GalloDaSballo Nov 1, 2024
f4b1419
chore: custom precision change
GalloDaSballo Nov 1, 2024
04d1b16
chore: remove property that doesn't really work
GalloDaSballo Nov 1, 2024
1ea9df5
feat: add a few revert tests that can help detect overflows
GalloDaSballo Nov 1, 2024
7c96bc7
chore: undo de-optimizations
GalloDaSballo Nov 1, 2024
f210e44
chore: recommendation
GalloDaSballo Nov 1, 2024
e553fea
chore: cleanup
GalloDaSballo Nov 1, 2024
d762007
fix: approve for bribes
GalloDaSballo Nov 2, 2024
1219a02
fix: clamped claim
GalloDaSballo Nov 2, 2024
7742491
chore: cleanup and simplification
GalloDaSballo Nov 2, 2024
4ded0c7
feat: repro for claim bribe
GalloDaSballo Nov 2, 2024
d60587e
feat: extra config for running locally
GalloDaSballo Nov 2, 2024
28d6425
chore: undo config changes
GalloDaSballo Nov 2, 2024
d904b26
feat: add solvency tests for Governance
GalloDaSballo Nov 2, 2024
96de709
feat: insolvency optimization tests
GalloDaSballo Nov 2, 2024
1f0b310
chore: improved coverage and removed canaries
GalloDaSballo Nov 2, 2024
2bfb2b9
chore: make precision public
GalloDaSballo Nov 3, 2024
eb42ca1
chore: local echidna command
GalloDaSballo Nov 3, 2024
4d5526f
cleanup: remove debugged properties
GalloDaSballo Nov 3, 2024
b1b5b2b
fix: remove BI11
GalloDaSballo Nov 3, 2024
36ae4c6
fix: Total and Initiative math can only be compared on state, not sna…
GalloDaSballo Nov 3, 2024
f483b06
fix: optimization properties to use state values
GalloDaSballo Nov 3, 2024
29471b2
chore: comment on possible more checks
GalloDaSballo Nov 3, 2024
9bc1dd9
Merge pull request #68 from liquity/fix-temp-invariant-align-bribes
GalloDaSballo Nov 3, 2024
2f1abb7
chore: informational
GalloDaSballo Nov 3, 2024
c4814c0
:wq
GalloDaSballo Nov 3, 2024
e3c7bdd
feat: assertion for optimization properties
GalloDaSballo Nov 3, 2024
56a0595
feat: additional explicit overflow checks
GalloDaSballo Nov 3, 2024
1a1bd97
chore: more lower threshold canaries for optimiztion
GalloDaSballo Nov 3, 2024
7f6565c
chore: config reset
GalloDaSballo Nov 4, 2024
f510655
feat: debugging of broken properties
GalloDaSballo Nov 4, 2024
9eee4bf
chore: force specific repro
GalloDaSballo Nov 4, 2024
44359de
feat: rational checks for deposit max
GalloDaSballo Nov 4, 2024
dd6edc6
feat: repro for max impact
GalloDaSballo Nov 4, 2024
de65c89
feat: fully shrunken sequence
GalloDaSballo Nov 4, 2024
ebba848
feat: remove incorrectly broken properties
GalloDaSballo Nov 4, 2024
953a3ef
chore: remove unused tests
GalloDaSballo Nov 4, 2024
a255f05
Merge pull request #70 from liquity/fix-broken-properties
danielattilasimon Nov 13, 2024
77ecdbd
chore: forge fmt
danielattilasimon Nov 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Invariants.MD
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Snapshot Solvency

uint256 claim = _votesForInitiativeSnapshot.votes * boldAccrued / _votesSnapshot.votes;
For each initiative this is what the value is
If the initiative is "Claimable" this is what it receives
The call never reverts
The sum of claims is less than the boldAccrued

Veto consistency

31 changes: 31 additions & 0 deletions ToFix.MD
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
- Add properties check to ensure that the math is sound <- HUGE, let's add it now

A vote is: User TS * Votes
So an allocation should use that
We need to remove the data from the valid allocation
And not from a random one

I think the best test is to simply store the contribution done
And see whether removing it is idempotent

We would need a ton of work to make it even better


Specifically, if a user removes their votes, we need to see that reflect correctly
Because that's key

- From there, try fixing with a reset on deposit and withdraw

- Add a test that checks every: initiative, user allocation, ensure they are zero after a deposit and a withdrawal
- Add a test that checks every: X, ensure they use the correct TS

- From there, reason around the deeper rounding errors



Optimizations
Put the data in the storage
Remove all castings that are not safe
Invariant test it

--
8 changes: 5 additions & 3 deletions echidna.yaml
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
testMode: "assertion"
prefix: "crytic_"
testMode: "property"
prefix: "optimize_"
coverage: true
corpusDir: "echidna"
balanceAddr: 0x1043561a8829300000
balanceContract: 0x1043561a8829300000
filterFunctions: []
cryticArgs: ["--foundry-compile-all"]
cryticArgs: ["--foundry-compile-all"]

shrinkLimit: 100000
49 changes: 34 additions & 15 deletions src/BribeInitiative.sol
Original file line number Diff line number Diff line change
Expand Up @@ -45,19 +45,19 @@ contract BribeInitiative is IInitiative, IBribeInitiative {
}

/// @inheritdoc IBribeInitiative
function totalLQTYAllocatedByEpoch(uint16 _epoch) external view returns (uint88, uint32) {
function totalLQTYAllocatedByEpoch(uint16 _epoch) external view returns (uint88, uint120) {
return _loadTotalLQTYAllocation(_epoch);
}

/// @inheritdoc IBribeInitiative
function lqtyAllocatedByUserAtEpoch(address _user, uint16 _epoch) external view returns (uint88, uint32) {
function lqtyAllocatedByUserAtEpoch(address _user, uint16 _epoch) external view returns (uint88, uint120) {
return _loadLQTYAllocation(_user, _epoch);
}

/// @inheritdoc IBribeInitiative
function depositBribe(uint128 _boldAmount, uint128 _bribeTokenAmount, uint16 _epoch) external {
uint16 epoch = governance.epoch();
require(_epoch >= epoch, "BribeInitiative: only-future-epochs");
require(_epoch >= epoch, "BribeInitiative: now-or-future-epochs");

Bribe memory bribe = bribeByEpoch[_epoch];
bribe.boldAmount += _boldAmount;
Expand All @@ -70,6 +70,8 @@ contract BribeInitiative is IInitiative, IBribeInitiative {
bribeToken.safeTransferFrom(msg.sender, address(this), _bribeTokenAmount);
}

uint256 constant TIMESTAMP_PRECISION = 1e26;

function _claimBribe(
address _user,
uint16 _epoch,
Expand Down Expand Up @@ -98,11 +100,24 @@ contract BribeInitiative is IInitiative, IBribeInitiative {
"BribeInitiative: invalid-prev-total-lqty-allocation-epoch"
);

(uint88 totalLQTY, uint32 totalAverageTimestamp) = _decodeLQTYAllocation(totalLQTYAllocation.value);
uint240 totalVotes = governance.lqtyToVotes(totalLQTY, block.timestamp, totalAverageTimestamp);
(uint88 totalLQTY, uint120 totalAverageTimestamp) = _decodeLQTYAllocation(totalLQTYAllocation.value);

// NOTE: SCALING!!! | The timestamp will work until type(uint32).max | After which the math will eventually overflow
uint120 scaledEpochEnd = (
uint120(governance.EPOCH_START()) + uint120(_epoch) * uint120(governance.EPOCH_DURATION())
) * uint120(TIMESTAMP_PRECISION);

/// @audit User Invariant
assert(totalAverageTimestamp <= scaledEpochEnd);

uint240 totalVotes = governance.lqtyToVotes(totalLQTY, scaledEpochEnd, totalAverageTimestamp);
if (totalVotes != 0) {
(uint88 lqty, uint32 averageTimestamp) = _decodeLQTYAllocation(lqtyAllocation.value);
uint240 votes = governance.lqtyToVotes(lqty, block.timestamp, averageTimestamp);
(uint88 lqty, uint120 averageTimestamp) = _decodeLQTYAllocation(lqtyAllocation.value);

/// @audit Governance Invariant
assert(averageTimestamp <= scaledEpochEnd);

uint240 votes = governance.lqtyToVotes(lqty, scaledEpochEnd, averageTimestamp);
boldAmount = uint256(bribe.boldAmount) * uint256(votes) / uint256(totalVotes);
bribeTokenAmount = uint256(bribe.bribeTokenAmount) * uint256(votes) / uint256(totalVotes);
}
Expand All @@ -126,13 +141,17 @@ contract BribeInitiative is IInitiative, IBribeInitiative {
bribeTokenAmount += bribeTokenAmount_;
}

// NOTE: Due to rounding errors in the `averageTimestamp` bribes may slightly overpay compared to what they have allocated
// We cap to the available amount for this reason
// The error should be below 10 LQTY per annum, in the worst case
if (boldAmount != 0) {
uint256 max = bold.balanceOf(address(this));
if (boldAmount > max) {
boldAmount = max;
}
bold.safeTransfer(msg.sender, boldAmount);
}

if (bribeTokenAmount != 0) {
uint256 max = bribeToken.balanceOf(address(this));
if (bribeTokenAmount > max) {
Expand All @@ -148,10 +167,10 @@ contract BribeInitiative is IInitiative, IBribeInitiative {
/// @inheritdoc IInitiative
function onUnregisterInitiative(uint16) external virtual override onlyGovernance {}

function _setTotalLQTYAllocationByEpoch(uint16 _epoch, uint88 _lqty, uint32 _averageTimestamp, bool _insert)
function _setTotalLQTYAllocationByEpoch(uint16 _epoch, uint88 _lqty, uint120 _averageTimestamp, bool _insert)
private
{
uint224 value = (uint224(_lqty) << 32) | _averageTimestamp;
uint224 value = _encodeLQTYAllocation(_lqty, _averageTimestamp);
if (_insert) {
totalLQTYAllocationByEpoch.insert(_epoch, value, 0);
} else {
Expand All @@ -164,10 +183,10 @@ contract BribeInitiative is IInitiative, IBribeInitiative {
address _user,
uint16 _epoch,
uint88 _lqty,
uint32 _averageTimestamp,
uint120 _averageTimestamp,
bool _insert
) private {
uint224 value = (uint224(_lqty) << 32) | _averageTimestamp;
uint224 value = _encodeLQTYAllocation(_lqty, _averageTimestamp);
if (_insert) {
lqtyAllocationByUserAtEpoch[_user].insert(_epoch, value, 0);
} else {
Expand All @@ -176,20 +195,20 @@ contract BribeInitiative is IInitiative, IBribeInitiative {
emit ModifyLQTYAllocation(_user, _epoch, _lqty, _averageTimestamp);
}

function _encodeLQTYAllocation(uint88 _lqty, uint32 _averageTimestamp) private pure returns (uint224) {
function _encodeLQTYAllocation(uint88 _lqty, uint120 _averageTimestamp) private pure returns (uint224) {
return EncodingDecodingLib.encodeLQTYAllocation(_lqty, _averageTimestamp);
}

function _decodeLQTYAllocation(uint224 _value) private pure returns (uint88, uint32) {
function _decodeLQTYAllocation(uint224 _value) private pure returns (uint88, uint120) {
return EncodingDecodingLib.decodeLQTYAllocation(_value);
}

function _loadTotalLQTYAllocation(uint16 _epoch) private view returns (uint88, uint32) {
function _loadTotalLQTYAllocation(uint16 _epoch) private view returns (uint88, uint120) {
require(_epoch <= governance.epoch(), "No future Lookup");
return _decodeLQTYAllocation(totalLQTYAllocationByEpoch.items[_epoch].value);
}

function _loadLQTYAllocation(address _user, uint16 _epoch) private view returns (uint88, uint32) {
function _loadLQTYAllocation(address _user, uint16 _epoch) private view returns (uint88, uint120) {
require(_epoch <= governance.epoch(), "No future Lookup");
return _decodeLQTYAllocation(lqtyAllocationByUserAtEpoch[_user].items[_epoch].value);
}
Expand Down
11 changes: 9 additions & 2 deletions src/CurveV2GaugeRewards.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,23 @@ contract CurveV2GaugeRewards is BribeInitiative {
_depositIntoGauge(_bold);
}

// TODO: If this is capped, we may need to donate here, so cap it here as well
function _depositIntoGauge(uint256 amount) internal {
uint256 total = amount + remainder;

// For small donations queue them into the contract
if (amount < duration * 1000) {
if (total < duration * 1000) {
remainder += amount;
return;
}

uint256 total = amount + remainder;
remainder = 0;

uint256 available = bold.balanceOf(address(this));
if (available < total) {
total = available; // Cap due to rounding error causing a bit more bold being given away
}

bold.approve(address(gauge), total);
gauge.deposit_reward_token(address(bold), total, duration);

Expand Down
Loading
Loading