-
Notifications
You must be signed in to change notification settings - Fork 414
[RFC] Constraint discovery using symbolic execution #1409
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
base: master
Are you sure you want to change the base?
[RFC] Constraint discovery using symbolic execution #1409
Conversation
|
Made some modification to make it work with this example to obtain upper bounds for ticks: pragma solidity ^0.8.13;
contract C {
uint160 internal currentTick;
function useTickForSomething(int24 tick) public {
currentTick = getSqrtRatioAtTick(tick);
}
/**
* @notice Minimum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**-128
*/
int24 internal constant _MIN_TICK = -887272;
/**
* @notice Maximum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**128
*/
int24 internal constant _MAX_TICK = -_MIN_TICK;
function getSqrtRatioAtTick(
int24 tick
) internal pure returns (uint160 sqrtPriceX96) {
uint256 absTick = tick < 0 ? uint256(-int256(tick)) : uint256(int256(tick));
require(absTick <= uint256(int256(_MAX_TICK)), 'T'); // TODO need convert int24 to uint256
return 1; // CHANGE: Introduced early return
uint256 ratio = absTick & 0x1 != 0
? 0xfffcb933bd6fad37aa2d162d1a594001
: 0x100000000000000000000000000000000;
if (absTick & 0x2 != 0) {
ratio = (ratio * 0xfff97272373d413259a46990580e213a) >> 128;
}
if (absTick & 0x4 != 0) {
ratio = (ratio * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128;
}
if (absTick & 0x8 != 0) {
ratio = (ratio * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128;
}
if (absTick & 0x10 != 0) {
ratio = (ratio * 0xffcb9843d60f6159c9db58835c926644) >> 128;
}
if (absTick & 0x20 != 0) {
ratio = (ratio * 0xff973b41fa98c081472e6896dfb254c0) >> 128;
}
if (absTick & 0x40 != 0) {
ratio = (ratio * 0xff2ea16466c96a3843ec78b326b52861) >> 128;
}
if (absTick & 0x80 != 0) {
ratio = (ratio * 0xfe5dee046a99a2a811c461f1969c3053) >> 128;
}
if (absTick & 0x100 != 0) {
ratio = (ratio * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128;
}
if (absTick & 0x200 != 0) {
ratio = (ratio * 0xf987a7253ac413176f2b074cf7815e54) >> 128;
}
if (absTick & 0x400 != 0) {
ratio = (ratio * 0xf3392b0822b70005940c7a398e4b70f3) >> 128;
}
if (absTick & 0x800 != 0) {
ratio = (ratio * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128;
}
if (absTick & 0x1000 != 0) {
ratio = (ratio * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128;
}
if (absTick & 0x2000 != 0) {
ratio = (ratio * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128;
}
if (absTick & 0x4000 != 0) {
ratio = (ratio * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128;
}
if (absTick & 0x8000 != 0) {
ratio = (ratio * 0x31be135f97d08fd981231505542fcfa6) >> 128;
}
if (absTick & 0x10000 != 0) {
ratio = (ratio * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128;
}
if (absTick & 0x20000 != 0) {
ratio = (ratio * 0x5d6af8dedb81196699c329225ee604) >> 128;
}
if (absTick & 0x40000 != 0) {
ratio = (ratio * 0x2216e584f5fa1ea926041bedfe98) >> 128;
}
if (absTick & 0x80000 != 0) {
ratio = (ratio * 0x48a170391f7dc42444e8fa2) >> 128;
}
if (tick > 0) ratio = type(uint256).max / ratio;
// this divides by 1<<32 rounding up to go from a Q128.128 to a Q128.96
// we then downcast because we know the result always fits within 160 bits due to our tick input constraint
// we round up in the division so getTickAtSqrtRatio of the output price is always consistent
sqrtPriceX96 = uint160((ratio >> 32) + (ratio % (1 << 32) == 0 ? 0 : 1));
}
}The result of running this is: This is only the upper bound. The lower bound is harder since there is a sign operation, and it must be inferred that the program is computing the absolute value (which is hard). In any case, note that I introduced an early return. I need to check exactly how the partial computations are obtained from the model returned by hevm (otherwise, the contraints are empty 🤔 ). |
|
The issue with the partials is solved in argotorg/hevm#822, waiting for upstream response. |
|
The latest revision is able to handle partials executions and also advanced interval resolution with basic arithmetics. For instance: constructor() {
balance[address(this)] = 0x999;
balance[msg.sender] = 0x888;
actor = msg.sender;
}
function oneConstraint(uint256 x1, uint256 x2, uint256 x3, uint256 x4) external {
require(x4 >= balance[actor]);
require(!(x3 - 100 >= 0x99));
...
}the response is: |
This is some very experimental code produced after a discussion started by @GalloDaSballo asking if symbolic execution could be used to extract simple constraints for inputs that do NOT trigger reverts (and speed up the exploration). I created this very crude prototype to experiment with this idea. This is an example on how to use it:
echidna model.sol --sym-exec true --test-mode assertion --format textIt will output a lot of data with raw SMT data. In particular, you should look for this:
The code we have right now only provides an approximation for the constraints (as you can see in this example). It can be extended for more operands (probably you can vibe code it).
If this results in useful constraints, this simple range information could be passed to the fuzzing engine to be able to discard or generate valid values faster. Right now, there is nothing else except printing constraints.
This PR should be merged as it is, but it is intended to open for the discussion and experimentation.