Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ fs_permissions = [

{ access = "read", path = "./test/mechanisms/huff-vrgda/mocks/LinearVRGDAWrappers.huff" },
{ access = "read", path = "./test/mechanisms/huff-vrgda/mocks/LogisticVRGDAWrappers.huff" },
{ access = "read", path = "./test/mechanisms/huff-vrgda/mocks/SignedWadMathWrappers.huff" },

{ access = "read", path = "./test/proxies/mocks/ClonesWrappers.huff" },
{ access = "read", path = "./test/proxies/mocks/ProxyWrappers.huff" },
Expand Down
2 changes: 1 addition & 1 deletion src/mechanisms/huff-vrgda/LogisticVRGDA.huff
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
[LOGISTIC_LIMIT] // [logisticLimit, sold, 1e18, timeScale]
add // [sold + logisticLimit, 1e18, timeScale]
[LOGISTIC_LIMIT_DOUBLED] // [logisticLimitDoubled, (sold+logisticLimit), 1e18, timeScale]
UNSAFE_WAD_DIV() // [(logisticLimitDoubled / (sold + logisticLimit)), 1e18, timeScale]
UNSAFE_DIV() // [(logisticLimitDoubled / (sold + logisticLimit)), 1e18, timeScale]
sub // [((logisticLimitDoubled / (sold + logisticLimit)) - 1e18), timeScale]

LN_WAD(fail) // [wadLn((logisticLimitDoubled / (sold + logisticLimit)) - 1e18), timeScale]
Expand Down
12 changes: 6 additions & 6 deletions src/mechanisms/huff-vrgda/SignedWadMath.huff
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,18 @@
[WAD] mul
}

#define macro toDaysWadUnsafe() = takes (1) returns (1) {
[WAD] mul [DAY] div
#define macro TO_DAYS_WAD_UNSAFE() = takes (1) returns (1) {
[WAD] mul [DAY] swap1 div
}


#define macro FROM_DAYS_WAD_UNSAFE() = takes (1) returns (1) {
[DAY] mul [WAD] div
[DAY] mul [WAD] swap1 div
}


#define macro UNSAFE_WAD_MUL() = takes (2) returns (1) {
mul [WAD] sdiv
mul [WAD] swap1 sdiv
}


Expand Down Expand Up @@ -129,7 +129,7 @@
dup2 // [x, 0xb17217f7d1cf79abc9e3b398, x]
0x60 shl // [x << 96, 0xb17217f7d1cf79abc9e3b398, x]
sdiv // [x << 96 / 0xb17217f7d1cf79abc9e3b398, x]
0x7ffffff20f9306d2eea00000 // [2**95, x << 96 / 0xb17217f7d1cf79abc9e3b398, x]
0x800000000000000000000000 // [2**95, x << 96 / 0xb17217f7d1cf79abc9e3b398, x]
add // [2**95 + x << 96 / 0xb17217f7d1cf79abc9e3b398, x]
0x60 sar // [(2**95 + x << 96 / 0xb17217f7d1cf79abc9e3b398) >> 96, x]

Expand Down Expand Up @@ -314,7 +314,7 @@
add // [q, p, k, x]

dup4 mul // [q * x, p, k, x]
0x60 shr // [(q * x) >> 0x60, p, k, x]
0x60 sar // [(q * x) >> 0x60, p, k, x]
0x0a0f742023def783a307a986912e
add // [q, p, k, x]

Expand Down
4 changes: 2 additions & 2 deletions test/mechanisms/huff-vrgda/LinearVRGDA.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ contract LinearVRGDATest is Test {
vm.warp(block.timestamp + fromDaysWadUnsafe(vrgda.getTargetSaleTime(1e18)));

uint256 cost = vrgda.getVRGDAPrice(toDaysWadUnsafe(block.timestamp), 0);
assertEq(cost / 0.00001e18, uint256(vrgda.targetPrice()) / 0.00001e18);
assertApproxEqRel(cost, uint256(vrgda.targetPrice()), 0.0001e18); // ~lossy day-conversion warp
}

function testPricingBasic() public {
Expand All @@ -82,7 +82,7 @@ contract LinearVRGDATest is Test {
vm.warp(block.timestamp + timeDelta);

uint256 cost = vrgda.getVRGDAPrice(toDaysWadUnsafe(block.timestamp), numMint);
assertEq(cost / 0.00001e18, uint256(vrgda.targetPrice()) / 0.00001e18);
assertApproxEqRel(cost, uint256(vrgda.targetPrice()), 0.0001e18); // ~lossy day-conversion warp
}

function testAlwaysTargetPriceInRightConditions(uint256 sold) public {
Expand Down
7 changes: 4 additions & 3 deletions test/mechanisms/huff-vrgda/LogisticVRGDA.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ contract LogisticVRGDATest is Test {
int256 wadMaxSellable = toWadUnsafe(MAX_SELLABLE);
targetPrice = 69.42e18;
logisticLimit = int256(wadMaxSellable + 1e18);
logisticLimitDoubled = int256(wadMaxSellable * 2e18);
logisticLimitDoubled = int256(logisticLimit * 2e18);
timeScale = 0.0023e18;

// calculate the decay constant
Expand Down Expand Up @@ -79,7 +79,8 @@ contract LogisticVRGDATest is Test {
vm.warp(block.timestamp + fromDaysWadUnsafe(vrgda.getTargetSaleTime(1e18)));

uint256 cost = vrgda.getVRGDAPrice(toDaysWadUnsafe(block.timestamp), 0);
assertEq(cost / 0.0000001e18, uint256(vrgda.targetPrice()) / 0.0000001e18);
// Approximate because the warp round-trips through integer seconds (lossy day-conversion).
assertApproxEqRel(cost, uint256(vrgda.targetPrice()), 0.0001e18);
}

function testPricingBasic() public {
Expand All @@ -92,7 +93,7 @@ contract LogisticVRGDATest is Test {
uint256 cost = vrgda.getVRGDAPrice(toDaysWadUnsafe(block.timestamp), numMint);

// Equal within 2 percent since num mint is rounded from true decimal amount.
assertEq(cost / 0.02e18, uint256(vrgda.targetPrice()) / 0.02e18);
assertApproxEqRel(cost, uint256(vrgda.targetPrice()), 0.02e18);
}

function testGetTargetSaleTimeDoesNotRevertEarly() public view {
Expand Down
228 changes: 228 additions & 0 deletions test/mechanisms/huff-vrgda/SignedWadMath.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;

import "forge-std/Test.sol";
import "foundry-huff/HuffDeployer.sol";

import {
toWadUnsafe,
toDaysWadUnsafe,
fromDaysWadUnsafe,
unsafeWadMul,
unsafeWadDiv,
unsafeDiv,
wadMul,
wadDiv,
wadExp,
wadLn
} from "./utils/SignedWadMath.sol";

/// @notice The Huff SignedWadMath surface under test.
interface ISignedWadMath {
function toWadUnsafe(uint256) external returns (int256);
function toDaysWadUnsafe(uint256) external returns (int256);
function fromDaysWadUnsafe(int256) external returns (uint256);
function unsafeWadMul(int256, int256) external returns (int256);
function unsafeWadDiv(int256, int256) external returns (int256);
function unsafeDiv(int256, int256) external returns (int256);
function wadMul(int256, int256) external returns (int256);
function wadDiv(int256, int256) external returns (int256);
function wadExp(int256) external returns (int256);
function wadLn(int256) external returns (int256);
function powWad(int256, int256) external returns (int256);
function log2(uint256) external returns (uint256);
}

/// @notice Wraps the reference free functions as *external* functions so the
/// reverting ones can be probed with try/catch for revert-parity.
contract Ref {
function toWadUnsafe(uint256 x) external pure returns (int256) { return toWadUnsafe(x); }
function toDaysWadUnsafe(uint256 x) external pure returns (int256) { return toDaysWadUnsafe(x); }
function fromDaysWadUnsafe(int256 x) external pure returns (uint256) { return fromDaysWadUnsafe(x); }
function unsafeWadMul(int256 x, int256 y) external pure returns (int256) { return unsafeWadMul(x, y); }
function unsafeWadDiv(int256 x, int256 y) external pure returns (int256) { return unsafeWadDiv(x, y); }
function unsafeDiv(int256 x, int256 y) external pure returns (int256) { return unsafeDiv(x, y); }
function wadMul(int256 x, int256 y) external pure returns (int256) { return wadMul(x, y); }
function wadDiv(int256 x, int256 y) external pure returns (int256) { return wadDiv(x, y); }
function wadExp(int256 x) external pure returns (int256) { return wadExp(x); }
function wadLn(int256 x) external pure returns (int256) { return wadLn(x); }

/// @dev Mirrors solmate's powWad and the Huff POW_WAD exactly:
/// expWad( sdiv(mul(lnWad(x), y), 1e18) )
function powWad(int256 x, int256 y) external pure returns (int256) {
int256 l = wadLn(x); // reverts if x <= 0
int256 z;
assembly { z := sdiv(mul(l, y), 1000000000000000000) }
return wadExp(z); // reverts on overflow
}

/// @dev floor(log2(x)); reverts on x == 0, matching the Huff LOG_2.
function log2(uint256 x) external pure returns (uint256 r) {
require(x != 0, "UNDEFINED");
while (x > 1) { x >>= 1; r++; }
}
}

contract SignedWadMathTest is Test {
ISignedWadMath huff;
Ref ref;

int256 constant WAD = 1e18;

function setUp() public {
ref = new Ref();
string memory wrapper = vm.readFile("test/mechanisms/huff-vrgda/mocks/SignedWadMathWrappers.huff");
huff = ISignedWadMath(
HuffDeployer.deploy_with_code("mechanisms/huff-vrgda/SignedWadMath", wrapper)
);
}

/* ------------------------------------------------------------------ */
/* NON-REVERTING OPS: EXACT MATCH */
/* ------------------------------------------------------------------ */

function testDiff_toWadUnsafe(uint256 x) public {
assertEq(huff.toWadUnsafe(x), ref.toWadUnsafe(x));
}

function testDiff_toDaysWadUnsafe(uint256 x) public {
assertEq(huff.toDaysWadUnsafe(x), ref.toDaysWadUnsafe(x));
}

function testDiff_fromDaysWadUnsafe(int256 x) public {
assertEq(huff.fromDaysWadUnsafe(x), ref.fromDaysWadUnsafe(x));
}

function testDiff_unsafeWadMul(int256 x, int256 y) public {
assertEq(huff.unsafeWadMul(x, y), ref.unsafeWadMul(x, y));
}

function testDiff_unsafeWadDiv(int256 x, int256 y) public {
assertEq(huff.unsafeWadDiv(x, y), ref.unsafeWadDiv(x, y));
}

function testDiff_unsafeDiv(int256 x, int256 y) public {
assertEq(huff.unsafeDiv(x, y), ref.unsafeDiv(x, y));
}

/* ------------------------------------------------------------------ */
/* REVERTING OPS: VALUE + REVERT PARITY */
/* ------------------------------------------------------------------ */

function testDiff_wadMul(int256 x, int256 y) public {
try ref.wadMul(x, y) returns (int256 e) {
assertEq(huff.wadMul(x, y), e);
} catch {
vm.expectRevert();
huff.wadMul(x, y);
}
}

function testDiff_wadDiv(int256 x, int256 y) public {
try ref.wadDiv(x, y) returns (int256 e) {
assertEq(huff.wadDiv(x, y), e);
} catch {
vm.expectRevert();
huff.wadDiv(x, y);
}
}

function testDiff_wadExp(int256 x) public {
try ref.wadExp(x) returns (int256 e) {
assertEq(huff.wadExp(x), e);
} catch {
vm.expectRevert();
huff.wadExp(x);
}
}

function testDiff_wadLn(int256 x) public {
try ref.wadLn(x) returns (int256 e) {
assertEq(huff.wadLn(x), e);
} catch {
vm.expectRevert();
huff.wadLn(x);
}
}

function testDiff_powWad(int256 x, int256 y) public {
// Keep the base positive and exponents in a sane band so a healthy
// fraction of runs exercise the non-reverting path; try/catch covers the rest.
x = bound(x, 1, int256(1e30));
y = bound(y, -20e18, 20e18);
try ref.powWad(x, y) returns (int256 e) {
assertEq(huff.powWad(x, y), e);
} catch {
vm.expectRevert();
huff.powWad(x, y);
}
}

function testDiff_log2(uint256 x) public {
try ref.log2(x) returns (uint256 e) {
assertEq(huff.log2(x), e);
} catch {
vm.expectRevert();
huff.log2(x);
}
}

/* ------------------------------------------------------------------ */
/* TARGETED DOMAIN-BOUNDARY CASES */
/* ------------------------------------------------------------------ */

function testExp_boundaries() public {
// Underflow boundary: x <= -42139678854452767551 returns 0.
assertEq(huff.wadExp(-42139678854452767551), 0);
assertEq(huff.wadExp(type(int256).min), 0);
assertEq(huff.wadExp(-42139678854452767551 + 1), ref.wadExp(-42139678854452767551 + 1));

// Overflow boundary: x >= 135305999368893231589 reverts.
vm.expectRevert();
huff.wadExp(135305999368893231589);
vm.expectRevert();
huff.wadExp(type(int256).max);

// Just below the overflow boundary must succeed and match.
assertEq(huff.wadExp(135305999368893231589 - 1), ref.wadExp(135305999368893231589 - 1));
}

function testLn_boundaries() public {
// Undefined for x <= 0.
vm.expectRevert();
huff.wadLn(0);
vm.expectRevert();
huff.wadLn(-1);
vm.expectRevert();
huff.wadLn(type(int256).min);

// ln(1e18) == 0, ln(1) is very negative — both must match the reference.
assertEq(huff.wadLn(WAD), ref.wadLn(WAD));
assertEq(huff.wadLn(1), ref.wadLn(1));
assertEq(huff.wadLn(type(int256).max), ref.wadLn(type(int256).max));
}

/* ------------------------------------------------------------------ */
/* KNOWN-ANSWER ANCHORS (vs real math) */
/* ------------------------------------------------------------------ */

function testExp_knownAnswers() public {
assertEq(huff.wadExp(0), WAD); // e^0 = 1
// e^1 ~= 2.718281828e18
assertApproxEqRel(huff.wadExp(WAD), 2718281828459045235, 1e12);
// e^-1 ~= 0.367879441e18
assertApproxEqRel(huff.wadExp(-WAD), 367879441171442321, 1e12);
}

function testLn_knownAnswers() public {
assertEq(huff.wadLn(WAD), 0); // ln(1) = 0
// ln(e) = 1e18 where e ~= 2.718281828e18
assertApproxEqRel(huff.wadLn(2718281828459045235), WAD, 1e12);
}

function testWadMul_knownAnswers() public {
assertEq(huff.wadMul(3 * WAD, 2 * WAD), 6 * WAD);
assertEq(huff.wadMul(-3 * WAD, 2 * WAD), -6 * WAD);
assertEq(huff.unsafeWadMul(3 * WAD, 2 * WAD), 6 * WAD);
}
}
63 changes: 63 additions & 0 deletions test/mechanisms/huff-vrgda/mocks/SignedWadMathWrappers.huff
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/// @title SignedWadMath Test Wrappers
/// @notice Exposes every SignedWadMath macro as an external function for
/// differential testing against the reference Solidity implementation.
/// @dev The body of src/mechanisms/huff-vrgda/SignedWadMath.huff is appended
/// automatically by HuffDeployer.deploy_with_code("mechanisms/huff-vrgda/SignedWadMath", ...).

#define function toWadUnsafe(uint256) nonpayable returns (int256)
#define function toDaysWadUnsafe(uint256) nonpayable returns (int256)
#define function fromDaysWadUnsafe(int256) nonpayable returns (uint256)
#define function unsafeWadMul(int256, int256) nonpayable returns (int256)
#define function unsafeWadDiv(int256, int256) nonpayable returns (int256)
#define function unsafeDiv(int256, int256) nonpayable returns (int256)
#define function wadMul(int256, int256) nonpayable returns (int256)
#define function wadDiv(int256, int256) nonpayable returns (int256)
#define function wadExp(int256) nonpayable returns (int256)
#define function wadLn(int256) nonpayable returns (int256)
#define function powWad(int256, int256) nonpayable returns (int256)
#define function log2(uint256) nonpayable returns (uint256)

#define macro MAIN() = takes (0) returns (0) {
0x00 calldataload 0xE0 shr // [selector]

dup1 __FUNC_SIG(toWadUnsafe) eq toWad jumpi
dup1 __FUNC_SIG(toDaysWadUnsafe) eq toDays jumpi
dup1 __FUNC_SIG(fromDaysWadUnsafe) eq fromDays jumpi
dup1 __FUNC_SIG(unsafeWadMul) eq uWadMul jumpi
dup1 __FUNC_SIG(unsafeWadDiv) eq uWadDiv jumpi
dup1 __FUNC_SIG(unsafeDiv) eq uDiv jumpi
dup1 __FUNC_SIG(wadMul) eq wMul jumpi
dup1 __FUNC_SIG(wadDiv) eq wDiv jumpi
dup1 __FUNC_SIG(wadExp) eq wExp jumpi
dup1 __FUNC_SIG(wadLn) eq wLn jumpi
dup1 __FUNC_SIG(powWad) eq wPow jumpi
dup1 __FUNC_SIG(log2) eq wLog2 jumpi

fail:
0x00 0x00 revert

toWad:
0x04 calldataload TO_WAD_UNSAFE() 0x00 mstore 0x20 0x00 return
toDays:
0x04 calldataload TO_DAYS_WAD_UNSAFE() 0x00 mstore 0x20 0x00 return
fromDays:
0x04 calldataload FROM_DAYS_WAD_UNSAFE() 0x00 mstore 0x20 0x00 return
uWadMul:
0x24 calldataload 0x04 calldataload UNSAFE_WAD_MUL() 0x00 mstore 0x20 0x00 return
uWadDiv:
0x24 calldataload 0x04 calldataload UNSAFE_WAD_DIV() 0x00 mstore 0x20 0x00 return
uDiv:
0x24 calldataload 0x04 calldataload UNSAFE_DIV() 0x00 mstore 0x20 0x00 return
wMul:
0x24 calldataload 0x04 calldataload WAD_MUL(fail) 0x00 mstore 0x20 0x00 return
wDiv:
0x24 calldataload 0x04 calldataload WAD_DIV(fail) 0x00 mstore 0x20 0x00 return
wExp:
0x04 calldataload EXP_WAD(fail) 0x00 mstore 0x20 0x00 return
wLn:
0x04 calldataload LN_WAD(fail) 0x00 mstore 0x20 0x00 return
wPow:
0x24 calldataload 0x04 calldataload POW_WAD(fail) 0x00 mstore 0x20 0x00 return
wLog2:
0x04 calldataload LOG_2(fail) 0x00 mstore 0x20 0x00 return
}
Loading