Skip to content

Commit a5ecf5d

Browse files
JhChoymds1ZeroEkkusu
authored
feat: implement boundInt (#253)
* feat: implement boundInt * test: add boundInt test * chore: roll back require statement of _bound * chore: add comments to boundInt * fix: fix boundInt overflow bug and add some edge case tests * refactor: change func name, boundInt => bound * feat: add console2.log(string,int256) * chore: add fn to console2.log * style: forge fmt * fix: use temp console workaround * fix: correct log Co-authored-by: Matt Solomon <[email protected]> Co-authored-by: Zero Ekkusu <[email protected]>
1 parent cd7d533 commit a5ecf5d

File tree

4 files changed

+147
-19
lines changed

4 files changed

+147
-19
lines changed

src/StdUtils.sol

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
// SPDX-License-Identifier: MIT
22
pragma solidity >=0.6.2 <0.9.0;
33

4-
import "./console2.sol";
4+
// TODO Remove import.
5+
import {Vm} from "./Vm.sol";
56

67
abstract contract StdUtils {
8+
Vm private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code")))));
9+
address private constant CONSOLE2_ADDRESS = 0x000000000000000000636F6e736F6c652e6c6f67;
10+
11+
uint256 private constant INT256_MIN_ABS =
12+
57896044618658097711785492504343953926634992332820282019728792003956564819968;
713
uint256 private constant UINT256_MAX =
814
115792089237316195423570985008687907853269984665640564039457584007913129639935;
915

1016
function _bound(uint256 x, uint256 min, uint256 max) internal pure virtual returns (uint256 result) {
1117
require(min <= max, "StdUtils bound(uint256,uint256,uint256): Max is less than min.");
12-
1318
// If x is between min and max, return x directly. This is to ensure that dictionary values
1419
// do not get shifted if the min is nonzero. More info: https://github.com/foundry-rs/forge-std/issues/188
1520
if (x >= min && x <= max) return x;
@@ -37,7 +42,28 @@ abstract contract StdUtils {
3742

3843
function bound(uint256 x, uint256 min, uint256 max) internal view virtual returns (uint256 result) {
3944
result = _bound(x, min, max);
40-
console2.log("Bound Result", result);
45+
console2_log("Bound Result", result);
46+
}
47+
48+
function bound(int256 x, int256 min, int256 max) internal view virtual returns (int256 result) {
49+
require(min <= max, "StdUtils bound(int256,int256,int256): Max is less than min.");
50+
51+
// Shifting all int256 values to uint256 to use _bound function. The range of two types are:
52+
// int256 : -(2**255) ~ (2**255 - 1)
53+
// uint256: 0 ~ (2**256 - 1)
54+
// So, add 2**255, INT256_MIN_ABS to the integer values.
55+
//
56+
// If the given integer value is -2**255, we cannot use `-uint256(-x)` because of the overflow.
57+
// So, use `~uint256(x) + 1` instead.
58+
uint256 _x = x < 0 ? (INT256_MIN_ABS - ~uint256(x) - 1) : (uint256(x) + INT256_MIN_ABS);
59+
uint256 _min = min < 0 ? (INT256_MIN_ABS - ~uint256(min) - 1) : (uint256(min) + INT256_MIN_ABS);
60+
uint256 _max = max < 0 ? (INT256_MIN_ABS - ~uint256(max) - 1) : (uint256(max) + INT256_MIN_ABS);
61+
62+
uint256 y = _bound(_x, _min, _max);
63+
64+
// To move it back to int256 value, subtract INT256_MIN_ABS at here.
65+
result = y < INT256_MIN_ABS ? int256(~(INT256_MIN_ABS - y) + 1) : int256(y - INT256_MIN_ABS);
66+
console2_log("Bound result", vm.toString(result));
4167
}
4268

4369
/// @dev Compute the address a contract will be deployed at for a given deployer address and nonce
@@ -82,4 +108,14 @@ abstract contract StdUtils {
82108
function addressFromLast20Bytes(bytes32 bytesValue) private pure returns (address) {
83109
return address(uint160(uint256(bytesValue)));
84110
}
111+
112+
function console2_log(string memory p0, uint256 p1) private view {
113+
(bool status,) = address(CONSOLE2_ADDRESS).staticcall(abi.encodeWithSignature("log(string,uint256)", p0, p1));
114+
status;
115+
}
116+
117+
function console2_log(string memory p0, string memory p1) private view {
118+
(bool status,) = address(CONSOLE2_ADDRESS).staticcall(abi.encodeWithSignature("log(string,string)", p0, p1));
119+
status;
120+
}
85121
}

src/console2.sol

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,10 @@ library console2 {
179179
_sendLogPayload(abi.encodeWithSignature("log(uint256)", p0));
180180
}
181181

182+
function log(int256 p0) internal view {
183+
_sendLogPayload(abi.encodeWithSignature("log(int256)", p0));
184+
}
185+
182186
function log(string memory p0) internal view {
183187
_sendLogPayload(abi.encodeWithSignature("log(string)", p0));
184188
}
@@ -211,6 +215,10 @@ library console2 {
211215
_sendLogPayload(abi.encodeWithSignature("log(string,uint256)", p0, p1));
212216
}
213217

218+
function log(string memory p0, int256 p1) internal view {
219+
_sendLogPayload(abi.encodeWithSignature("log(string,int256)", p0, p1));
220+
}
221+
214222
function log(string memory p0, string memory p1) internal view {
215223
_sendLogPayload(abi.encodeWithSignature("log(string,string)", p0, p1));
216224
}

test/StdCheats.t.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ contract StdCheatsTest is Test {
238238
assertTrue(gas_used_double + gas_used_single < gas_used_normal);
239239
}
240240

241-
function addInLoop() internal returns (uint256) {
241+
function addInLoop() internal pure returns (uint256) {
242242
uint256 b;
243243
for (uint256 i; i < 10000; i++) {
244244
b += i;

test/StdUtils.t.sol

Lines changed: 99 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,26 @@ import "../src/Test.sol";
55

66
contract StdUtilsTest is Test {
77
function testBound() public {
8-
assertEq(bound(5, 0, 4), 0);
9-
assertEq(bound(0, 69, 69), 69);
10-
assertEq(bound(0, 68, 69), 68);
11-
assertEq(bound(10, 150, 190), 174);
12-
assertEq(bound(300, 2800, 3200), 3107);
13-
assertEq(bound(9999, 1337, 6666), 4669);
8+
assertEq(bound(uint256(5), 0, 4), 0);
9+
assertEq(bound(uint256(0), 69, 69), 69);
10+
assertEq(bound(uint256(0), 68, 69), 68);
11+
assertEq(bound(uint256(10), 150, 190), 174);
12+
assertEq(bound(uint256(300), 2800, 3200), 3107);
13+
assertEq(bound(uint256(9999), 1337, 6666), 4669);
1414
}
1515

1616
function testBound_WithinRange() public {
17-
assertEq(bound(51, 50, 150), 51);
18-
assertEq(bound(51, 50, 150), bound(bound(51, 50, 150), 50, 150));
19-
assertEq(bound(149, 50, 150), 149);
20-
assertEq(bound(149, 50, 150), bound(bound(149, 50, 150), 50, 150));
17+
assertEq(bound(uint256(51), 50, 150), 51);
18+
assertEq(bound(uint256(51), 50, 150), bound(bound(uint256(51), 50, 150), 50, 150));
19+
assertEq(bound(uint256(149), 50, 150), 149);
20+
assertEq(bound(uint256(149), 50, 150), bound(bound(uint256(149), 50, 150), 50, 150));
2121
}
2222

2323
function testBound_EdgeCoverage() public {
24-
assertEq(bound(0, 50, 150), 50);
25-
assertEq(bound(1, 50, 150), 51);
26-
assertEq(bound(2, 50, 150), 52);
27-
assertEq(bound(3, 50, 150), 53);
24+
assertEq(bound(uint256(0), 50, 150), 50);
25+
assertEq(bound(uint256(1), 50, 150), 51);
26+
assertEq(bound(uint256(2), 50, 150), 52);
27+
assertEq(bound(uint256(3), 50, 150), 53);
2828
assertEq(bound(type(uint256).max, 50, 150), 150);
2929
assertEq(bound(type(uint256).max - 1, 50, 150), 149);
3030
assertEq(bound(type(uint256).max - 2, 50, 150), 148);
@@ -65,7 +65,7 @@ contract StdUtilsTest is Test {
6565

6666
function testCannotBoundMaxLessThanMin() public {
6767
vm.expectRevert(bytes("StdUtils bound(uint256,uint256,uint256): Max is less than min."));
68-
bound(5, 100, 10);
68+
bound(uint256(5), 100, 10);
6969
}
7070

7171
function testCannotBoundMaxLessThanMin(uint256 num, uint256 min, uint256 max) public {
@@ -74,6 +74,90 @@ contract StdUtilsTest is Test {
7474
bound(num, min, max);
7575
}
7676

77+
function testBoundInt() public {
78+
assertEq(bound(-3, 0, 4), 2);
79+
assertEq(bound(0, -69, -69), -69);
80+
assertEq(bound(0, -69, -68), -68);
81+
assertEq(bound(-10, 150, 190), 154);
82+
assertEq(bound(-300, 2800, 3200), 2908);
83+
assertEq(bound(9999, -1337, 6666), 1995);
84+
}
85+
86+
function testBoundInt_WithinRange() public {
87+
assertEq(bound(51, -50, 150), 51);
88+
assertEq(bound(51, -50, 150), bound(bound(51, -50, 150), -50, 150));
89+
assertEq(bound(149, -50, 150), 149);
90+
assertEq(bound(149, -50, 150), bound(bound(149, -50, 150), -50, 150));
91+
}
92+
93+
function testBoundInt_EdgeCoverage() public {
94+
assertEq(bound(type(int256).min, -50, 150), -50);
95+
assertEq(bound(type(int256).min + 1, -50, 150), -49);
96+
assertEq(bound(type(int256).min + 2, -50, 150), -48);
97+
assertEq(bound(type(int256).min + 3, -50, 150), -47);
98+
assertEq(bound(type(int256).min, 10, 150), 10);
99+
assertEq(bound(type(int256).min + 1, 10, 150), 11);
100+
assertEq(bound(type(int256).min + 2, 10, 150), 12);
101+
assertEq(bound(type(int256).min + 3, 10, 150), 13);
102+
103+
assertEq(bound(type(int256).max, -50, 150), 150);
104+
assertEq(bound(type(int256).max - 1, -50, 150), 149);
105+
assertEq(bound(type(int256).max - 2, -50, 150), 148);
106+
assertEq(bound(type(int256).max - 3, -50, 150), 147);
107+
assertEq(bound(type(int256).max, -50, -10), -10);
108+
assertEq(bound(type(int256).max - 1, -50, -10), -11);
109+
assertEq(bound(type(int256).max - 2, -50, -10), -12);
110+
assertEq(bound(type(int256).max - 3, -50, -10), -13);
111+
}
112+
113+
function testBoundInt_DistributionIsEven(int256 min, uint256 size) public {
114+
size = size % 100 + 1;
115+
min = bound(min, -int256(size / 2), int256(size - size / 2));
116+
int256 max = min + int256(size) - 1;
117+
int256 result;
118+
119+
for (uint256 i = 1; i <= size * 4; ++i) {
120+
// x > max
121+
result = bound(max + int256(i), min, max);
122+
assertEq(result, min + int256((i - 1) % size));
123+
// x < min
124+
result = bound(min - int256(i), min, max);
125+
assertEq(result, max - int256((i - 1) % size));
126+
}
127+
}
128+
129+
function testBoundInt(int256 num, int256 min, int256 max) public {
130+
if (min > max) (min, max) = (max, min);
131+
132+
int256 result = bound(num, min, max);
133+
134+
assertGe(result, min);
135+
assertLe(result, max);
136+
assertEq(result, bound(result, min, max));
137+
if (num >= min && num <= max) assertEq(result, num);
138+
}
139+
140+
function testBoundIntInt256Max() public {
141+
assertEq(bound(0, type(int256).max - 1, type(int256).max), type(int256).max - 1);
142+
assertEq(bound(1, type(int256).max - 1, type(int256).max), type(int256).max);
143+
}
144+
145+
function testBoundIntInt256Min() public {
146+
assertEq(bound(0, type(int256).min, type(int256).min + 1), type(int256).min);
147+
assertEq(bound(1, type(int256).min, type(int256).min + 1), type(int256).min + 1);
148+
}
149+
150+
function testCannotBoundIntMaxLessThanMin() public {
151+
vm.expectRevert(bytes("StdUtils bound(int256,int256,int256): Max is less than min."));
152+
bound(-5, 100, 10);
153+
}
154+
155+
function testCannotBoundIntMaxLessThanMin(int256 num, int256 min, int256 max) public {
156+
vm.assume(min > max);
157+
vm.expectRevert(bytes("StdUtils bound(int256,int256,int256): Max is less than min."));
158+
bound(num, min, max);
159+
}
160+
77161
function testGenerateCreateAddress() external {
78162
address deployer = 0x6C9FC64A53c1b71FB3f9Af64d1ae3A4931A5f4E9;
79163
uint256 nonce = 14;

0 commit comments

Comments
 (0)