Skip to content

Commit

Permalink
better test structures
Browse files Browse the repository at this point in the history
  • Loading branch information
jordaniza committed Sep 5, 2024
1 parent 7a5faab commit 02f42fd
Show file tree
Hide file tree
Showing 7 changed files with 278 additions and 58 deletions.
21 changes: 13 additions & 8 deletions src/escrow/increasing/QuadraticIncreasingEscrow.sol
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.17;

import {IVotingEscrowIncreasing as IVotingEscrow} from "./interfaces/IVotingEscrowIncreasing.sol";
// interfaces
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IVotingEscrowIncreasing as IVotingEscrow} from "@escrow-interfaces/IVotingEscrowIncreasing.sol";
import {IEscrowCurveIncreasing as IEscrowCurve} from "@escrow-interfaces/IEscrowCurveIncreasing.sol";

// libraries
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import {EpochDurationLib} from "@libs/EpochDurationLib.sol";
import {IEscrowCurveIncreasing as IEscrowCurve} from "./interfaces/IEscrowCurveIncreasing.sol";
import {SignedFixedPointMath} from "./SignedFixedPointMath.sol";
import {SignedFixedPointMath} from "@libs/SignedFixedPointMathLib.sol";

// contracts
import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";

import {console2 as console} from "forge-std/console2.sol";

Expand Down Expand Up @@ -128,7 +133,7 @@ contract QuadraticIncreasingEscrow is IEscrowCurve, ReentrancyGuard {

int256 t = SignedFixedPointMath.toFP(timeElapsed.toInt256());

// y = ax^2 + bx + c
// bias = a.t^2 + b.t + c
int256 bias = quadratic.mul(t.pow(SD2)).add(linear.mul(t)).add(const);
// never return negative values
// in the increasing case, this should never happen
Expand Down Expand Up @@ -161,7 +166,7 @@ contract QuadraticIncreasingEscrow is IEscrowCurve, ReentrancyGuard {

/// @notice Returns whether the NFT is warm
function isWarm(uint256 tokenId) public view returns (bool) {
uint256 _epoch = getPastUserPointIndex(tokenId, block.timestamp);
uint256 _epoch = _getPastUserPointIndex(tokenId, block.timestamp);
UserPoint memory point = _userPointHistory[tokenId][_epoch];
if (point.bias == 0) return false;
else return _isWarm(point, block.timestamp);
Expand All @@ -180,7 +185,7 @@ contract QuadraticIncreasingEscrow is IEscrowCurve, ReentrancyGuard {

/// @notice Binary search to get the user point index for a token id at or prior to a given timestamp
/// @dev If a user point does not exist prior to the timestamp, this will return 0.
function getPastUserPointIndex(uint256 _tokenId, uint256 _timestamp) internal view returns (uint256) {
function _getPastUserPointIndex(uint256 _tokenId, uint256 _timestamp) internal view returns (uint256) {
uint256 _userEpoch = userPointEpoch[_tokenId];
if (_userEpoch == 0) return 0;
// First check most recent balance
Expand All @@ -205,7 +210,7 @@ contract QuadraticIncreasingEscrow is IEscrowCurve, ReentrancyGuard {
}

function votingPowerAt(uint256 _tokenId, uint256 _t) external view returns (uint256) {
uint256 _epoch = getPastUserPointIndex(_tokenId, _t);
uint256 _epoch = _getPastUserPointIndex(_tokenId, _t);
// epoch 0 is an empty point
if (_epoch == 0) return 0;
UserPoint memory lastPoint = _userPointHistory[_tokenId][_epoch];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ pragma solidity ^0.8.0;

import "@solmate/utils/SignedWadMath.sol";

error NegativeBase();

// shared interface for fixed point math implementations.
library SignedFixedPointMath {
// solmate does this unchecked to save gas, easier to do this here
Expand Down Expand Up @@ -34,7 +36,7 @@ library SignedFixedPointMath {
}

function pow(int256 x, int256 y) internal pure returns (int256) {
require(x >= 0, "FixedPointMath: x < 0");
if (x < 0) revert NegativeBase();
if (x == 0) return 0;
return wadPow(x, y);
}
Expand Down
35 changes: 35 additions & 0 deletions test/escrow/curve/QuadraticCurveBase.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
pragma solidity ^0.8.17;

import {Test} from "forge-std/Test.sol";
import {console2 as console} from "forge-std/console2.sol";

import {QuadraticIncreasingEscrow, IVotingEscrow, IEscrowCurve} from "src/escrow/increasing/QuadraticIncreasingEscrow.sol";
import {IVotingEscrowIncreasing, ILockedBalanceIncreasing} from "src/escrow/increasing/interfaces/IVotingEscrowIncreasing.sol";

contract MockEscrow {
address public token;
QuadraticIncreasingEscrow public curve;

function setCurve(QuadraticIncreasingEscrow _curve) external {
curve = _curve;
}

function checkpoint(
uint256 _tokenId,
IVotingEscrow.LockedBalance memory _oldLocked,
IVotingEscrow.LockedBalance memory _newLocked
) external {
return curve.checkpoint(_tokenId, _oldLocked, _newLocked);
}
}

contract QuadraticCurveBase is Test, ILockedBalanceIncreasing {
QuadraticIncreasingEscrow internal curve;
MockEscrow internal escrow;

function setUp() public {
escrow = new MockEscrow();
curve = new QuadraticIncreasingEscrow(address(escrow));
escrow.setCurve(curve);
}
}
40 changes: 40 additions & 0 deletions test/escrow/curve/QuadraticCurveLogic.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
pragma solidity ^0.8.17;

import {console2 as console} from "forge-std/console2.sol";

import {QuadraticIncreasingEscrow, IVotingEscrow, IEscrowCurve} from "src/escrow/increasing/QuadraticIncreasingEscrow.sol";
import {IVotingEscrowIncreasing, ILockedBalanceIncreasing} from "src/escrow/increasing/interfaces/IVotingEscrowIncreasing.sol";
import {QuadraticCurveBase, MockEscrow} from "./QuadraticCurveBase.t.sol";

contract TestQuadraticIncreasingCurve is QuadraticCurveBase {
// check that our constants are initialized correctly
// check the escrow is set
function testEscrowInitializesCorrectly() public {
MockEscrow escrow = new MockEscrow();
QuadraticIncreasingEscrow curve_ = new QuadraticIncreasingEscrow(address(escrow));
assertEq(address(curve_.escrow()), address(escrow));
}
// validate the bias bounding works
// warmup: TODO - how do we ensure the warmup doesn't add to an epoch that snaps
// in the future
// warmup: variable warmup perid (create a setter)
// warmup: empty warmup period returns fase
// supplyAt reverts
// same block checkpointing overwrite user point history
// updating checkpoint with a lower balance
// updating checkpoint with a higher balance
// updating with the same balance
// only the escrow can call checkpoint
// point index with large number of points
// - if userepoch 0 return 0
// - if latest user epoch before ts, return the latest user epoch
// - implicit zero balance
// understand at what boundary the curve starts to break down by doing a very small and very large
// deposit
// test the bound bias caps at the boundary
// test that the cooldown correcty calculates
// test a checkpoint correctly saves the user point
// test that the cooldown is respected for the NFT balance
// test the fetched NFT balance from a point in timeFirst
// TODO: check aero tests for other ideas
}
Original file line number Diff line number Diff line change
@@ -1,38 +1,12 @@
pragma solidity ^0.8.17;

import {Test} from "forge-std/Test.sol";
import {console2 as console} from "forge-std/console2.sol";

import {QuadraticIncreasingEscrow, IVotingEscrow, IEscrowCurve} from "src/escrow/increasing/QuadraticIncreasingEscrow.sol";
import {IVotingEscrowIncreasing, ILockedBalanceIncreasing} from "src/escrow/increasing/interfaces/IVotingEscrowIncreasing.sol";
import {QuadraticCurveBase} from "./QuadraticCurveBase.t.sol";

contract MockEscrow {
address public token;
QuadraticIncreasingEscrow public curve;

function setCurve(QuadraticIncreasingEscrow _curve) external {
curve = _curve;
}

function checkpoint(
uint256 _tokenId,
IVotingEscrow.LockedBalance memory _oldLocked,
IVotingEscrow.LockedBalance memory _newLocked
) external {
return curve.checkpoint(_tokenId, _oldLocked, _newLocked);
}
}

contract TestLinearIncreasingCurve is Test, ILockedBalanceIncreasing {
QuadraticIncreasingEscrow curve;
MockEscrow escrow;

function setUp() public {
escrow = new MockEscrow();
curve = new QuadraticIncreasingEscrow(address(escrow));
escrow.setCurve(curve);
}

contract TestQuadraticIncreasingCurve is QuadraticCurveBase {
function test_votingPowerComputesCorrect() public {
/**
Period Result
Expand All @@ -53,8 +27,6 @@ contract TestLinearIncreasingCurve is Test, ILockedBalanceIncreasing {
uint256 cubic = uint256(coefficients[3]);

assertEq(const, amount);
// assertEq(const, amount);
// assertEq(const, amount);
assertEq(cubic, 0);

console.log("Coefficients: %st^2 + %st + %s", quadratic, linear, const);
Expand All @@ -66,6 +38,7 @@ contract TestLinearIncreasingCurve is Test, ILockedBalanceIncreasing {
console.log("Period: %d Voting Power Raw: %s\n", i, curve.getBiasUnbound(period, 100e18));
}

// uncomment to see the full curve
// for (uint i; i <= 14 * 6; i++) {
// uint day = i * 1 days;
// uint week = day / 7 days;
Expand All @@ -77,19 +50,6 @@ contract TestLinearIncreasingCurve is Test, ILockedBalanceIncreasing {
// }
}

// tests we can write
// 1. Precompute a set of values for a curve and check that they are correct for amounts
// 100 tokens, 420.696969696969696969 tokens, 1 token

// understand at what boundary the curve starts to break down by doing a very small and very large
// deposit

// test the bound bias caps at the boundary

// test that the cooldown correcty calculates

// test a checkpoint correctly saves the user point

// write a new checkpoint
function testWritesCheckpoint() public {
uint tokenIdFirst = 1;
Expand All @@ -99,13 +59,13 @@ contract TestLinearIncreasingCurve is Test, ILockedBalanceIncreasing {
uint start = 52 weeks;

// initial conditions, no balance
// assertEq(curve.votingPowerAt(tokenId, 0), 0, "Balance before deposit");
assertEq(curve.votingPowerAt(tokenIdFirst, 0), 0, "Balance before deposit");

vm.warp(start);
vm.roll(420);

// still no balance
// assertEq(curve.votingPowerAt(tokenId, 0), 0, "Balance before deposit");
assertEq(curve.votingPowerAt(tokenIdFirst, 0), 0, "Balance before deposit");

escrow.checkpoint(tokenIdFirst, LockedBalance(0, 0), LockedBalance(depositFirst, 0));
escrow.checkpoint(tokenIdSecond, LockedBalance(0, 0), LockedBalance(depositSecond, 0));
Expand Down Expand Up @@ -178,8 +138,4 @@ contract TestLinearIncreasingCurve is Test, ILockedBalanceIncreasing {
"Balance incorrect after 10 years"
);
}

// test the fetched NFT balance from a point in time

// test that the cooldown is respected for the NFT balance
}
92 changes: 92 additions & 0 deletions test/libs/EpochDurationLib.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.17;

import "forge-std/Test.sol"; // Assuming you're using Foundry for testing
import "@libs/EpochDurationLib.sol";

contract EpochDurationLibTest is Test {
using EpochDurationLib for uint256;

// Helper function to warp time for fuzzing
function _warpTo(uint64 _warp) internal {
vm.warp(_warp);
}

function testEpochStart(uint64 _warp) public {
_warpTo(_warp); // Warp to fuzzed timestamp
uint256 timestamp = block.timestamp;
uint256 epochStart = EpochDurationLib.epochStart(timestamp);

// The epoch start should be aligned to the start of the period
assertEq(epochStart, timestamp - (timestamp % EpochDurationLib.EPOCH_DURATION));
}

function testEpochNext(uint64 _warp) public {
_warpTo(_warp); // Warp to fuzzed timestamp
uint256 timestamp = block.timestamp;
uint256 nextEpoch = EpochDurationLib.epochNext(timestamp);

// Next epoch should start at the current epoch start + 2 weeks
uint256 expectedNextEpoch = EpochDurationLib.epochStart(timestamp) + EpochDurationLib.EPOCH_DURATION;
assertEq(nextEpoch, expectedNextEpoch);
}

function testEpochVoteStart(uint64 _warp) public {
_warpTo(_warp); // Warp to fuzzed timestamp
uint256 timestamp = block.timestamp;
uint256 voteStart = EpochDurationLib.epochVoteStart(timestamp);

// Vote start should be the start of the epoch + 1 hour
uint256 expectedVoteStart = EpochDurationLib.epochStart(timestamp) + 1 hours;
assertEq(voteStart, expectedVoteStart);
}

function testEpochVoteEnd(uint64 _warp) public {
_warpTo(_warp); // Warp to fuzzed timestamp
uint256 timestamp = block.timestamp;
uint256 voteEnd = EpochDurationLib.epochVoteEnd(timestamp);

// Vote end should be the start of the epoch + half the epoch duration - 1 hour
uint256 expectedVoteEnd = EpochDurationLib.epochStart(timestamp) +
(EpochDurationLib.EPOCH_DURATION / 2) -
1 hours;
assertEq(voteEnd, expectedVoteEnd);
}

function testVotingActiveDuringVotePeriod(uint64 _warp, uint32 _voting) public {
vm.assume(_voting < 1 weeks - 1 hours); // Ensure voting period is less than a week
_warpTo(_warp); // Warp to fuzzed timestamp
uint256 timestamp = block.timestamp;
uint256 voteStart = EpochDurationLib.epochVoteStart(timestamp);

// Simulate a time during the voting period
uint256 voteActiveTimestamp = voteStart + _voting;
bool isVotingActive = EpochDurationLib.votingActive(voteActiveTimestamp);

assertTrue(isVotingActive);
}

function testVotingActiveOutsideVotePeriod(uint64 _warp) public {
_warpTo(_warp); // Warp to fuzzed timestamp
uint256 timestamp = block.timestamp;
uint256 voteEnd = EpochDurationLib.epochVoteEnd(timestamp);

// Simulate a time after the voting period has ended
uint256 afterVoteTimestamp = voteEnd + 1 hours;
bool isVotingActive = EpochDurationLib.votingActive(afterVoteTimestamp);

assertFalse(isVotingActive);
}

function testVotingNotActiveBeforeVoteStart(uint64 _warp) public {
_warpTo(_warp); // Warp to fuzzed timestamp
uint256 timestamp = block.timestamp;
uint256 voteStart = EpochDurationLib.epochVoteStart(timestamp);

// Simulate a time before the voting period starts
uint256 beforeVoteStartTimestamp = voteStart - 1 hours;
bool isVotingActive = EpochDurationLib.votingActive(beforeVoteStartTimestamp);

assertFalse(isVotingActive);
}
}
Loading

0 comments on commit 02f42fd

Please sign in to comment.