Skip to content

Commit

Permalink
Fuzzing + gas consumption tracked, updated README
Browse files Browse the repository at this point in the history
  • Loading branch information
0xknxwledge committed Jul 22, 2024
1 parent 0717ac0 commit 241a1f3
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 197 deletions.
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
- -1e20 ≤ μ ≤ 1e20
- 0 < σ ≤ 1e19
- x in [-1e23, 1e23]
- High-precision and gas efficient computation using 128-bit IEEE 754 floating-point arithmetic
- Max absolute error relative to errcw/gaussian on the order of 1e-17 (check Gauss.t.sol to verify and test out other parameterizations and realizations)
- High-precision and gas efficient computation using 128-bit IEEE 754 floating-point arithmetic and Horner's Method for a complementary
error function polynomial approximation
- Median fuzz test consumes ~53000 Gwei, has relative absolute error ~1e-16 compared to errcw/gaussian
- Run "forge test --match-test testFuzzedCDFPrecisionStats -vvvvv" to verify

## Usage

Expand All @@ -34,4 +36,4 @@ This project is licensed under the MIT License - see the LICENSE file for detail
- Inspired by [primitivefinance/solstat](https://github.com/primitivefinance/solstat) and [errcw/gaussian](https://github.com/errcw/gaussian)
- Uses [ABDK Libraries for Solidity](https://github.com/abdk-consulting/abdk-libraries-solidity)
- Submission for the following 2024 Paradigm Fellowship technical question:
- Implement a maximally optimized gaussian CDF on the EVM for arbitrary 18 decimal fixed point parameters x, μ, σ. Assume -1e20 ≤ μ ≤ 1e20 and 0 < σ ≤ 1e19. Should have an error less than 1e-8 vs
- Implement a maximally optimized gaussian CDF on the EVM for arbitrary 18 decimal fixed point parameters x, μ, σ. Assume -1e20 ≤ μ ≤ 1e20 and 0 < σ ≤ 1e19. Should have an error less than 1e-8 vs errcw/gaussian for all x on the interval [-1e23, 1e23].
126 changes: 1 addition & 125 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,132 +11,8 @@ remappings = [
"abdk-libraries/=lib/abdk-libraries/"
]
auto_detect_remappings = true
libraries = []
cache = true
cache_path = "cache"
broadcast = "broadcast"
allow_paths = []
include_paths = []
skip = []
force = false
evm_version = "paris"
gas_reports = ["*"]
gas_reports_ignore = []
auto_detect_solc = true
offline = false
optimizer = true
optimizer_runs = 200
verbosity = 0
ignored_error_codes = [
"license",
"code-size",
"init-code-size",
"transient-storage",
]
ignored_warnings_from = []
deny_warnings = false
test_failures_file = "cache/test-failures"
show_progress = false
unchecked_cheatcode_artifacts = false
create2_library_salt = "0x0000000000000000000000000000000000000000000000000000000000000000"
ffi = true
always_use_create_2_factory = false
prompt_timeout = 120
sender = "0x1804c8ab1f12e6bbf3894d4083f33e07309d1f38"
tx_origin = "0x1804c8ab1f12e6bbf3894d4083f33e07309d1f38"
initial_balance = "0xffffffffffffffffffffffff"
block_number = 1
gas_limit = 1073741824
block_base_fee_per_gas = 0
block_coinbase = "0x0000000000000000000000000000000000000000"
block_timestamp = 1
block_difficulty = 0
block_prevrandao = "0x0000000000000000000000000000000000000000000000000000000000000000"
memory_limit = 134217728
extra_output = []
extra_output_files = []
names = false
sizes = false
via_ir = true
ast = false
no_storage_caching = false
no_rpc_rate_limit = false
use_literal_content = false
bytecode_hash = "ipfs"
cbor_metadata = true
sparse_mode = false
build_info = false
legacy_assertions = false
assertions_revert = true
disable_block_gas_limit = false
prague = false
isolate = false

[[profile.default.fs_permissions]]
access = "read"
path = "out"

[profile.default.rpc_storage_caching]
chains = "all"
endpoints = "all"

[fmt]
line_length = 120
tab_width = 4
bracket_spacing = false
int_types = "long"
multiline_func_header = "attributes_first"
quote_style = "double"
number_underscore = "preserve"
hex_underscore = "remove"
single_line_statement_blocks = "preserve"
override_spacing = false
wrap_comments = false
ignore = []
contract_new_lines = false
sort_imports = false

[doc]
out = "docs"
title = ""
book = "book.toml"
homepage = "README.md"
ignore = []

[fuzz]
runs = 256
max_test_rejects = 65536
dictionary_weight = 40
include_storage = true
include_push_bytes = true
max_fuzz_dictionary_addresses = 15728640
max_fuzz_dictionary_values = 6553600
gas_report_samples = 256
failure_persist_dir = "cache/fuzz"
failure_persist_file = "failures"
show_logs = false

[invariant]
runs = 256
depth = 500
fail_on_revert = false
call_override = false
dictionary_weight = 80
include_storage = true
include_push_bytes = true
max_fuzz_dictionary_addresses = 15728640
max_fuzz_dictionary_values = 6553600
shrink_run_limit = 5000
max_assume_rejects = 65536
gas_report_samples = 256
failure_persist_dir = "cache/invariant"

[labels]

[vyper]

[bind_json]
out = "utils/JsonBindings.sol"
include = []
exclude = []

runs = 100
128 changes: 59 additions & 69 deletions test/Gauss.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,79 +7,69 @@ import "../src/DegeGauss.sol";
contract DegeGaussTest is Test {
using DegeGauss for int256;

function testCDFAccuracy() public {
// Test cases
int256[] memory zValues = new int256[](17);
zValues[0] = -8e18;
zValues[1] = -7e18;
zValues[2] = -6e18;
zValues[3] = -5e18;
zValues[4] = -4e18;
zValues[5] = -3e18;
zValues[6] = -2e18;
zValues[7] = -1e18;
zValues[8] = 0;
zValues[9] = 1e18;
zValues[10] = 2e18;
zValues[11] = 3e18;
zValues[12] = 4e18;
zValues[13] = 5e18;
zValues[14] = 6e18;
zValues[15] = 7e18;
zValues[16] = 8e18;
function testFuzzedCDFPrecision(int256 mu, uint256 sigma, int256 x) public
{
// Bound the input parameters according to the specification
mu = bound(mu, -1e20, 1e20);
sigma = bound(sigma, 1, 1e19);
x = bound(x, -1e23, 1e23);

// Measure gas consumption
uint256 gasBefore = gasleft();

// Calculate CDF using Solidity implementation
uint256 solidityResult = DegeGauss.cdf(x, mu, sigma);

// Calculate gas consumed
uint256 gasConsumed = gasBefore - gasleft();

// Prepare JavaScript code for comparison
// Modified to accept standard deviation and return 0 for values that cannot be converted to 18 fixed point precision
string memory jsCode = string(abi.encodePacked(
"const erfc=function(x){const z=Math.abs(x),t=1/(1+z/2),r=t*Math.exp(-z*z-1.26551223+t*(1.00002368+t*(0.37409196+t*(0.09678418+t*(-0.18628806+t*(0.27886807+t*(-1.13520398+t*(1.48851587+t*(-0.82215223+t*0.17087277)))))))));return x>=0?r:2-r};",
"const Gaussian=function(mean,standardDeviation){this.mean=mean;this.standardDeviation=standardDeviation};",
"Gaussian.prototype.cdf=function(x){return 0.5*erfc(-((x)-this.mean)/(this.standardDeviation*Math.sqrt(2)))};",
"const gaussian=function(mean,standardDeviation){return new Gaussian(mean,standardDeviation)};",
"const g=gaussian(",
vm.toString(mu),
",",
vm.toString(sigma),
");",
"const result = g.cdf(",
vm.toString(x),
");",
"const fixedPointResult = result < 1e-18 ? 0 : (result >= 1 ? 1 : result);",
"console.log(JSON.stringify(fixedPointResult));"
));

string[] memory inputs = new string[](3);
inputs[0] = "node";
inputs[1] = "-e";
inputs[2] = jsCode;
uint256 jsResultParsed = parseJsonResult(string(vm.ffi(inputs)));

// Calculate the absolute error
uint256 diff = solidityResult >= jsResultParsed ?
solidityResult - jsResultParsed :
jsResultParsed - solidityResult;

for (uint i = 0; i < zValues.length; i++) {
int256 z = zValues[i];
uint256 solidityResult = DegeGauss.cdf(z, 0, 1e18);

string memory jsCode = string(abi.encodePacked(
getGaussianJs(),
vm.toString(int(z)),
")));"
));

string[] memory inputs = new string[](3);
inputs[0] = "node";
inputs[1] = "-e";
inputs[2] = jsCode;

bytes memory jsResult = vm.ffi(inputs);
uint256 jsResultParsed = parseJsonResult(string(jsResult));

// Calculate the absolute error
uint256 diff = solidityResult >= jsResultParsed ?
solidityResult - jsResultParsed :
jsResultParsed - solidityResult;

console.log("z (in 18 decimal precision):", z);
console.log("Solidity result:", solidityResult);
console.log("JS result:", jsResultParsed);
console.log("Absolute Error:", diff);
console.log("---");

// Assert that the difference is less than 1e10 (1e-8 in our fixed-point representation)
assertLe(diff, 1e10, "CDF approximation differs by more than 1e-8");
}
}
// Log results
console.log("x:", x);
console.log("mu:", mu);
console.log("sigma:", sigma);
console.log("Solidity result:", solidityResult);
console.log("JS result:", jsResultParsed);
console.log("Absolute Error:", diff);
console.log("Gas consumed:", gasConsumed);
console.log("---");

// Assert that the difference is less than 1e10 (1e-8 in our fixed-point representation)
assertLe(diff, 1e10, "CDF approximation differs by more than 1e-8");

// Modified slightly by adding x/1e18 so we can normalize in JS and not lose precision
// Also changed from gaussian(mean, variance) to gaussian(mean, standardDeviation)
// CHANGE 'const g = gaussian(mu,sigma)' IF TESTING OTHER PARAMETERIZATIONS!!!
function getGaussianJs() internal pure returns (string memory) {
return string(
abi.encodePacked(
"const erfc=function(x){const z=Math.abs(x),t=1/(1+z/2),r=t*Math.exp(-z*z-1.26551223+t*(1.00002368+t*(0.37409196+t*(0.09678418+t*(-0.18628806+t*(0.27886807+t*(-1.13520398+t*(1.48851587+t*(-0.82215223+t*0.17087277)))))))));return x>=0?r:2-r};",
"const Gaussian=function(mean,standardDeviation){this.mean=mean;this.standardDeviation=standardDeviation};",
"Gaussian.prototype.cdf=function(x){return 0.5*erfc(-((x/1e18)-this.mean)/(this.standardDeviation*Math.sqrt(2)))};",
"const gaussian=function(mean,standardDeviation){return new Gaussian(mean,standardDeviation)};",
"const g=gaussian(0,1);",
"console.log(JSON.stringify(g.cdf("
)
);
}


function parseJsonResult(string memory result) internal pure returns (uint256) {
function parseJsonResult(string memory result) internal pure returns (uint256)
{
bytes memory resultBytes = bytes(result);
uint256 value = 0;
uint256 decimalPlace = 0;
Expand Down

0 comments on commit 241a1f3

Please sign in to comment.