Files
core/lib/forge-std/src/Test.sol
2022-09-16 12:51:34 -04:00

1139 lines
37 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.9.0;
pragma experimental ABIEncoderV2;
import "./Script.sol";
import "ds-test/test.sol";
// Wrappers around Cheatcodes to avoid footguns
abstract contract Test is DSTest, Script {
using stdStorage for StdStorage;
uint256 internal constant UINT256_MAX =
115792089237316195423570985008687907853269984665640564039457584007913129639935;
StdStorage internal stdstore;
/*//////////////////////////////////////////////////////////////////////////
STD-LOGS
//////////////////////////////////////////////////////////////////////////*/
event log_array(uint256[] val);
event log_array(int256[] val);
event log_array(address[] val);
event log_named_array(string key, uint256[] val);
event log_named_array(string key, int256[] val);
event log_named_array(string key, address[] val);
/*//////////////////////////////////////////////////////////////////////////
STD-CHEATS
//////////////////////////////////////////////////////////////////////////*/
// Skip forward or rewind time by the specified number of seconds
function skip(uint256 time) internal {
vm.warp(block.timestamp + time);
}
function rewind(uint256 time) internal {
vm.warp(block.timestamp - time);
}
// Setup a prank from an address that has some ether
function hoax(address who) internal {
vm.deal(who, 1 << 128);
vm.prank(who);
}
function hoax(address who, uint256 give) internal {
vm.deal(who, give);
vm.prank(who);
}
function hoax(address who, address origin) internal {
vm.deal(who, 1 << 128);
vm.prank(who, origin);
}
function hoax(address who, address origin, uint256 give) internal {
vm.deal(who, give);
vm.prank(who, origin);
}
// Start perpetual prank from an address that has some ether
function startHoax(address who) internal {
vm.deal(who, 1 << 128);
vm.startPrank(who);
}
function startHoax(address who, uint256 give) internal {
vm.deal(who, give);
vm.startPrank(who);
}
// Start perpetual prank from an address that has some ether
// tx.origin is set to the origin parameter
function startHoax(address who, address origin) internal {
vm.deal(who, 1 << 128);
vm.startPrank(who, origin);
}
function startHoax(address who, address origin, uint256 give) internal {
vm.deal(who, give);
vm.startPrank(who, origin);
}
function changePrank(address who) internal {
vm.stopPrank();
vm.startPrank(who);
}
// creates a labeled address and the corresponding private key
function makeAddrAndKey(string memory name) internal returns(address addr, uint256 privateKey) {
privateKey = uint256(keccak256(abi.encodePacked(name)));
addr = vm.addr(privateKey);
vm.label(addr, name);
}
// creates a labeled address
function makeAddr(string memory name) internal returns(address addr) {
(addr,) = makeAddrAndKey(name);
}
// DEPRECATED: Use `deal` instead
function tip(address token, address to, uint256 give) internal {
emit log_named_string("WARNING", "Test tip(address,address,uint256): The `tip` stdcheat has been deprecated. Use `deal` instead.");
stdstore
.target(token)
.sig(0x70a08231)
.with_key(to)
.checked_write(give);
}
// The same as Vm's `deal`
// Use the alternative signature for ERC20 tokens
function deal(address to, uint256 give) internal {
vm.deal(to, give);
}
// Set the balance of an account for any ERC20 token
// Use the alternative signature to update `totalSupply`
function deal(address token, address to, uint256 give) internal {
deal(token, to, give, false);
}
function deal(address token, address to, uint256 give, bool adjust) internal {
// get current balance
(, bytes memory balData) = token.call(abi.encodeWithSelector(0x70a08231, to));
uint256 prevBal = abi.decode(balData, (uint256));
// update balance
stdstore
.target(token)
.sig(0x70a08231)
.with_key(to)
.checked_write(give);
// update total supply
if(adjust){
(, bytes memory totSupData) = token.call(abi.encodeWithSelector(0x18160ddd));
uint256 totSup = abi.decode(totSupData, (uint256));
if(give < prevBal) {
totSup -= (prevBal - give);
} else {
totSup += (give - prevBal);
}
stdstore
.target(token)
.sig(0x18160ddd)
.checked_write(totSup);
}
}
function bound(uint256 x, uint256 min, uint256 max) internal virtual returns (uint256 result) {
require(min <= max, "Test bound(uint256,uint256,uint256): Max is less than min.");
uint256 size = max - min;
if (size == 0)
{
result = min;
}
else if (size == UINT256_MAX)
{
result = x;
}
else
{
++size; // make `max` inclusive
uint256 mod = x % size;
result = min + mod;
}
emit log_named_uint("Bound Result", result);
}
// Deploy a contract by fetching the contract bytecode from
// the artifacts directory
// e.g. `deployCode(code, abi.encode(arg1,arg2,arg3))`
function deployCode(string memory what, bytes memory args)
internal
returns (address addr)
{
bytes memory bytecode = abi.encodePacked(vm.getCode(what), args);
/// @solidity memory-safe-assembly
assembly {
addr := create(0, add(bytecode, 0x20), mload(bytecode))
}
require(
addr != address(0),
"Test deployCode(string,bytes): Deployment failed."
);
}
function deployCode(string memory what)
internal
returns (address addr)
{
bytes memory bytecode = vm.getCode(what);
/// @solidity memory-safe-assembly
assembly {
addr := create(0, add(bytecode, 0x20), mload(bytecode))
}
require(
addr != address(0),
"Test deployCode(string): Deployment failed."
);
}
/// deploy contract with value on construction
function deployCode(string memory what, bytes memory args, uint256 val)
internal
returns (address addr)
{
bytes memory bytecode = abi.encodePacked(vm.getCode(what), args);
/// @solidity memory-safe-assembly
assembly {
addr := create(val, add(bytecode, 0x20), mload(bytecode))
}
require(
addr != address(0),
"Test deployCode(string,bytes,uint256): Deployment failed."
);
}
function deployCode(string memory what, uint256 val)
internal
returns (address addr)
{
bytes memory bytecode = vm.getCode(what);
/// @solidity memory-safe-assembly
assembly {
addr := create(val, add(bytecode, 0x20), mload(bytecode))
}
require(
addr != address(0),
"Test deployCode(string,uint256): Deployment failed."
);
}
/*//////////////////////////////////////////////////////////////////////////
STD-ASSERTIONS
//////////////////////////////////////////////////////////////////////////*/
function fail(string memory err) internal virtual {
emit log_named_string("Error", err);
fail();
}
function assertFalse(bool data) internal virtual {
assertTrue(!data);
}
function assertFalse(bool data, string memory err) internal virtual {
assertTrue(!data, err);
}
function assertEq(bool a, bool b) internal {
if (a != b) {
emit log ("Error: a == b not satisfied [bool]");
emit log_named_string (" Expected", b ? "true" : "false");
emit log_named_string (" Actual", a ? "true" : "false");
fail();
}
}
function assertEq(bool a, bool b, string memory err) internal {
if (a != b) {
emit log_named_string("Error", err);
assertEq(a, b);
}
}
function assertEq(bytes memory a, bytes memory b) internal {
assertEq0(a, b);
}
function assertEq(bytes memory a, bytes memory b, string memory err) internal {
assertEq0(a, b, err);
}
function assertEq(uint256[] memory a, uint256[] memory b) internal {
if (keccak256(abi.encode(a)) != keccak256(abi.encode(b))) {
emit log("Error: a == b not satisfied [uint[]]");
emit log_named_array(" Expected", b);
emit log_named_array(" Actual", a);
fail();
}
}
function assertEq(int256[] memory a, int256[] memory b) internal {
if (keccak256(abi.encode(a)) != keccak256(abi.encode(b))) {
emit log("Error: a == b not satisfied [int[]]");
emit log_named_array(" Expected", b);
emit log_named_array(" Actual", a);
fail();
}
}
function assertEq(address[] memory a, address[] memory b) internal {
if (keccak256(abi.encode(a)) != keccak256(abi.encode(b))) {
emit log("Error: a == b not satisfied [address[]]");
emit log_named_array(" Expected", b);
emit log_named_array(" Actual", a);
fail();
}
}
function assertEq(uint256[] memory a, uint256[] memory b, string memory err) internal {
if (keccak256(abi.encode(a)) != keccak256(abi.encode(b))) {
emit log_named_string("Error", err);
assertEq(a, b);
}
}
function assertEq(int256[] memory a, int256[] memory b, string memory err) internal {
if (keccak256(abi.encode(a)) != keccak256(abi.encode(b))) {
emit log_named_string("Error", err);
assertEq(a, b);
}
}
function assertEq(address[] memory a, address[] memory b, string memory err) internal {
if (keccak256(abi.encode(a)) != keccak256(abi.encode(b))) {
emit log_named_string("Error", err);
assertEq(a, b);
}
}
function assertEqUint(uint256 a, uint256 b) internal {
assertEq(uint256(a), uint256(b));
}
function assertApproxEqAbs(
uint256 a,
uint256 b,
uint256 maxDelta
) internal virtual {
uint256 delta = stdMath.delta(a, b);
if (delta > maxDelta) {
emit log ("Error: a ~= b not satisfied [uint]");
emit log_named_uint (" Expected", b);
emit log_named_uint (" Actual", a);
emit log_named_uint (" Max Delta", maxDelta);
emit log_named_uint (" Delta", delta);
fail();
}
}
function assertApproxEqAbs(
uint256 a,
uint256 b,
uint256 maxDelta,
string memory err
) internal virtual {
uint256 delta = stdMath.delta(a, b);
if (delta > maxDelta) {
emit log_named_string ("Error", err);
assertApproxEqAbs(a, b, maxDelta);
}
}
function assertApproxEqAbs(
int256 a,
int256 b,
uint256 maxDelta
) internal virtual {
uint256 delta = stdMath.delta(a, b);
if (delta > maxDelta) {
emit log ("Error: a ~= b not satisfied [int]");
emit log_named_int (" Expected", b);
emit log_named_int (" Actual", a);
emit log_named_uint (" Max Delta", maxDelta);
emit log_named_uint (" Delta", delta);
fail();
}
}
function assertApproxEqAbs(
int256 a,
int256 b,
uint256 maxDelta,
string memory err
) internal virtual {
uint256 delta = stdMath.delta(a, b);
if (delta > maxDelta) {
emit log_named_string ("Error", err);
assertApproxEqAbs(a, b, maxDelta);
}
}
function assertApproxEqRel(
uint256 a,
uint256 b,
uint256 maxPercentDelta // An 18 decimal fixed point number, where 1e18 == 100%
) internal virtual {
if (b == 0) return assertEq(a, b); // If the expected is 0, actual must be too.
uint256 percentDelta = stdMath.percentDelta(a, b);
if (percentDelta > maxPercentDelta) {
emit log ("Error: a ~= b not satisfied [uint]");
emit log_named_uint (" Expected", b);
emit log_named_uint (" Actual", a);
emit log_named_decimal_uint (" Max % Delta", maxPercentDelta, 18);
emit log_named_decimal_uint (" % Delta", percentDelta, 18);
fail();
}
}
function assertApproxEqRel(
uint256 a,
uint256 b,
uint256 maxPercentDelta, // An 18 decimal fixed point number, where 1e18 == 100%
string memory err
) internal virtual {
if (b == 0) return assertEq(a, b, err); // If the expected is 0, actual must be too.
uint256 percentDelta = stdMath.percentDelta(a, b);
if (percentDelta > maxPercentDelta) {
emit log_named_string ("Error", err);
assertApproxEqRel(a, b, maxPercentDelta);
}
}
function assertApproxEqRel(
int256 a,
int256 b,
uint256 maxPercentDelta
) internal virtual {
if (b == 0) return assertEq(a, b); // If the expected is 0, actual must be too.
uint256 percentDelta = stdMath.percentDelta(a, b);
if (percentDelta > maxPercentDelta) {
emit log ("Error: a ~= b not satisfied [int]");
emit log_named_int (" Expected", b);
emit log_named_int (" Actual", a);
emit log_named_decimal_uint(" Max % Delta", maxPercentDelta, 18);
emit log_named_decimal_uint(" % Delta", percentDelta, 18);
fail();
}
}
function assertApproxEqRel(
int256 a,
int256 b,
uint256 maxPercentDelta,
string memory err
) internal virtual {
if (b == 0) return assertEq(a, b); // If the expected is 0, actual must be too.
uint256 percentDelta = stdMath.percentDelta(a, b);
if (percentDelta > maxPercentDelta) {
emit log_named_string ("Error", err);
assertApproxEqRel(a, b, maxPercentDelta);
}
}
/*//////////////////////////////////////////////////////////////
JSON PARSING
//////////////////////////////////////////////////////////////*/
// Data structures to parse Transaction objects from the broadcast artifact
// that conform to EIP1559. The Raw structs is what is parsed from the JSON
// and then converted to the one that is used by the user for better UX.
struct RawTx1559 {
string[] arguments;
address contractAddress;
string contractName;
// json value name = function
string functionSig;
bytes32 hash;
// json value name = tx
RawTx1559Detail txDetail;
// json value name = type
string opcode;
}
struct RawTx1559Detail {
AccessList[] accessList;
bytes data;
address from;
bytes gas;
bytes nonce;
address to;
bytes txType;
bytes value;
}
struct Tx1559 {
string[] arguments;
address contractAddress;
string contractName;
string functionSig;
bytes32 hash;
Tx1559Detail txDetail;
string opcode;
}
struct Tx1559Detail {
AccessList[] accessList;
bytes data;
address from;
uint256 gas;
uint256 nonce;
address to;
uint256 txType;
uint256 value;
}
// Data structures to parse Transaction objects from the broadcast artifact
// that DO NOT conform to EIP1559. The Raw structs is what is parsed from the JSON
// and then converted to the one that is used by the user for better UX.
struct TxLegacy{
string[] arguments;
address contractAddress;
string contractName;
string functionSig;
string hash;
string opcode;
TxDetailLegacy transaction;
}
struct TxDetailLegacy{
AccessList[] accessList;
uint256 chainId;
bytes data;
address from;
uint256 gas;
uint256 gasPrice;
bytes32 hash;
uint256 nonce;
bytes1 opcode;
bytes32 r;
bytes32 s;
uint256 txType;
address to;
uint8 v;
uint256 value;
}
struct AccessList{
address accessAddress;
bytes32[] storageKeys;
}
// Data structures to parse Receipt objects from the broadcast artifact.
// The Raw structs is what is parsed from the JSON
// and then converted to the one that is used by the user for better UX.
struct RawReceipt {
bytes32 blockHash;
bytes blockNumber;
address contractAddress;
bytes cumulativeGasUsed;
bytes effectiveGasPrice;
address from;
bytes gasUsed;
RawReceiptLog[] logs;
bytes logsBloom;
bytes status;
address to;
bytes32 transactionHash;
bytes transactionIndex;
}
struct Receipt {
bytes32 blockHash;
uint256 blockNumber;
address contractAddress;
uint256 cumulativeGasUsed;
uint256 effectiveGasPrice;
address from;
uint256 gasUsed;
ReceiptLog[] logs;
bytes logsBloom;
uint256 status;
address to;
bytes32 transactionHash;
uint256 transactionIndex;
}
// Data structures to parse the entire broadcast artifact, assuming the
// transactions conform to EIP1559.
struct EIP1559ScriptArtifact {
string[] libraries;
string path;
string[] pending;
Receipt[] receipts;
uint256 timestamp;
Tx1559[] transactions;
TxReturn[] txReturns;
}
struct RawEIP1559ScriptArtifact {
string[] libraries;
string path;
string[] pending;
RawReceipt[] receipts;
TxReturn[] txReturns;
uint256 timestamp;
RawTx1559[] transactions;
}
struct RawReceiptLog {
// json value = address
address logAddress;
bytes32 blockHash;
bytes blockNumber;
bytes data;
bytes logIndex;
bool removed;
bytes32[] topics;
bytes32 transactionHash;
bytes transactionIndex;
bytes transactionLogIndex;
}
struct ReceiptLog {
// json value = address
address logAddress;
bytes32 blockHash;
uint256 blockNumber;
bytes data;
uint256 logIndex;
bytes32[] topics;
uint256 transactionIndex;
uint256 transactionLogIndex;
bool removed;
}
struct TxReturn {
string internalType;
string value;
}
function readEIP1559ScriptArtifact(string memory path)
internal
returns(EIP1559ScriptArtifact memory)
{
string memory data = vm.readFile(path);
bytes memory parsedData = vm.parseJson(data);
RawEIP1559ScriptArtifact memory rawArtifact = abi.decode(parsedData, (RawEIP1559ScriptArtifact));
EIP1559ScriptArtifact memory artifact;
artifact.libraries = rawArtifact.libraries;
artifact.path = rawArtifact.path;
artifact.timestamp = rawArtifact.timestamp;
artifact.pending = rawArtifact.pending;
artifact.txReturns = rawArtifact.txReturns;
artifact.receipts = rawToConvertedReceipts(rawArtifact.receipts);
artifact.transactions = rawToConvertedEIPTx1559s(rawArtifact.transactions);
return artifact;
}
function rawToConvertedEIPTx1559s(RawTx1559[] memory rawTxs)
internal pure
returns (Tx1559[] memory)
{
Tx1559[] memory txs = new Tx1559[](rawTxs.length);
for (uint i; i < rawTxs.length; i++) {
txs[i] = rawToConvertedEIPTx1559(rawTxs[i]);
}
return txs;
}
function rawToConvertedEIPTx1559(RawTx1559 memory rawTx)
internal pure
returns (Tx1559 memory)
{
Tx1559 memory transaction;
transaction.arguments = rawTx.arguments;
transaction.contractName = rawTx.contractName;
transaction.functionSig = rawTx.functionSig;
transaction.hash= rawTx.hash;
transaction.txDetail = rawToConvertedEIP1559Detail(rawTx.txDetail);
transaction.opcode= rawTx.opcode;
return transaction;
}
function rawToConvertedEIP1559Detail(RawTx1559Detail memory rawDetail)
internal pure
returns (Tx1559Detail memory)
{
Tx1559Detail memory txDetail;
txDetail.data = rawDetail.data;
txDetail.from = rawDetail.from;
txDetail.to = rawDetail.to;
txDetail.nonce = bytesToUint(rawDetail.nonce);
txDetail.txType = bytesToUint(rawDetail.txType);
txDetail.value = bytesToUint(rawDetail.value);
txDetail.gas = bytesToUint(rawDetail.gas);
txDetail.accessList = rawDetail.accessList;
return txDetail;
}
function readTx1559s(string memory path)
internal
returns (Tx1559[] memory)
{
string memory deployData = vm.readFile(path);
bytes memory parsedDeployData =
vm.parseJson(deployData, ".transactions");
RawTx1559[] memory rawTxs = abi.decode(parsedDeployData, (RawTx1559[]));
return rawToConvertedEIPTx1559s(rawTxs);
}
function readTx1559(string memory path, uint256 index)
internal
returns (Tx1559 memory)
{
string memory deployData = vm.readFile(path);
string memory key = string(abi.encodePacked(".transactions[",vm.toString(index), "]"));
bytes memory parsedDeployData =
vm.parseJson(deployData, key);
RawTx1559 memory rawTx = abi.decode(parsedDeployData, (RawTx1559));
return rawToConvertedEIPTx1559(rawTx);
}
// Analogous to readTransactions, but for receipts.
function readReceipts(string memory path)
internal
returns (Receipt[] memory)
{
string memory deployData = vm.readFile(path);
bytes memory parsedDeployData = vm.parseJson(deployData, ".receipts");
RawReceipt[] memory rawReceipts = abi.decode(parsedDeployData, (RawReceipt[]));
return rawToConvertedReceipts(rawReceipts);
}
function readReceipt(string memory path, uint index)
internal
returns (Receipt memory)
{
string memory deployData = vm.readFile(path);
string memory key = string(abi.encodePacked(".receipts[",vm.toString(index), "]"));
bytes memory parsedDeployData = vm.parseJson(deployData, key);
RawReceipt memory rawReceipt = abi.decode(parsedDeployData, (RawReceipt));
return rawToConvertedReceipt(rawReceipt);
}
function rawToConvertedReceipts(RawReceipt[] memory rawReceipts)
internal pure
returns(Receipt[] memory)
{
Receipt[] memory receipts = new Receipt[](rawReceipts.length);
for (uint i; i < rawReceipts.length; i++) {
receipts[i] = rawToConvertedReceipt(rawReceipts[i]);
}
return receipts;
}
function rawToConvertedReceipt(RawReceipt memory rawReceipt)
internal pure
returns(Receipt memory)
{
Receipt memory receipt;
receipt.blockHash = rawReceipt.blockHash;
receipt.to = rawReceipt.to;
receipt.from = rawReceipt.from;
receipt.contractAddress = rawReceipt.contractAddress;
receipt.effectiveGasPrice = bytesToUint(rawReceipt.effectiveGasPrice);
receipt.cumulativeGasUsed= bytesToUint(rawReceipt.cumulativeGasUsed);
receipt.gasUsed = bytesToUint(rawReceipt.gasUsed);
receipt.status = bytesToUint(rawReceipt.status);
receipt.transactionIndex = bytesToUint(rawReceipt.transactionIndex);
receipt.blockNumber = bytesToUint(rawReceipt.blockNumber);
receipt.logs = rawToConvertedReceiptLogs(rawReceipt.logs);
receipt.logsBloom = rawReceipt.logsBloom;
receipt.transactionHash = rawReceipt.transactionHash;
return receipt;
}
function rawToConvertedReceiptLogs(RawReceiptLog[] memory rawLogs)
internal pure
returns (ReceiptLog[] memory)
{
ReceiptLog[] memory logs = new ReceiptLog[](rawLogs.length);
for (uint i; i < rawLogs.length; i++) {
logs[i].logAddress = rawLogs[i].logAddress;
logs[i].blockHash = rawLogs[i].blockHash;
logs[i].blockNumber = bytesToUint(rawLogs[i].blockNumber);
logs[i].data = rawLogs[i].data;
logs[i].logIndex = bytesToUint(rawLogs[i].logIndex);
logs[i].topics = rawLogs[i].topics;
logs[i].transactionIndex = bytesToUint(rawLogs[i].transactionIndex);
logs[i].transactionLogIndex = bytesToUint(rawLogs[i].transactionLogIndex);
logs[i].removed = rawLogs[i].removed;
}
return logs;
}
function bytesToUint(bytes memory b) internal pure returns (uint256){
uint256 number;
for (uint i=0; i < b.length; i++) {
number = number + uint(uint8(b[i]))*(2**(8*(b.length-(i+1))));
}
return number;
}
}
/*//////////////////////////////////////////////////////////////////////////
STD-ERRORS
//////////////////////////////////////////////////////////////////////////*/
library stdError {
bytes public constant assertionError = abi.encodeWithSignature("Panic(uint256)", 0x01);
bytes public constant arithmeticError = abi.encodeWithSignature("Panic(uint256)", 0x11);
bytes public constant divisionError = abi.encodeWithSignature("Panic(uint256)", 0x12);
bytes public constant enumConversionError = abi.encodeWithSignature("Panic(uint256)", 0x21);
bytes public constant encodeStorageError = abi.encodeWithSignature("Panic(uint256)", 0x22);
bytes public constant popError = abi.encodeWithSignature("Panic(uint256)", 0x31);
bytes public constant indexOOBError = abi.encodeWithSignature("Panic(uint256)", 0x32);
bytes public constant memOverflowError = abi.encodeWithSignature("Panic(uint256)", 0x41);
bytes public constant zeroVarError = abi.encodeWithSignature("Panic(uint256)", 0x51);
// DEPRECATED: Use Vm's `expectRevert` without any arguments instead
bytes public constant lowLevelError = bytes(""); // `0x`
}
/*//////////////////////////////////////////////////////////////////////////
STD-STORAGE
//////////////////////////////////////////////////////////////////////////*/
struct StdStorage {
mapping (address => mapping(bytes4 => mapping(bytes32 => uint256))) slots;
mapping (address => mapping(bytes4 => mapping(bytes32 => bool))) finds;
bytes32[] _keys;
bytes4 _sig;
uint256 _depth;
address _target;
bytes32 _set;
}
library stdStorage {
event SlotFound(address who, bytes4 fsig, bytes32 keysHash, uint slot);
event WARNING_UninitedSlot(address who, uint slot);
uint256 private constant UINT256_MAX = 115792089237316195423570985008687907853269984665640564039457584007913129639935;
int256 private constant INT256_MAX = 57896044618658097711785492504343953926634992332820282019728792003956564819967;
Vm private constant vm_std_store = Vm(address(uint160(uint256(keccak256('hevm cheat code')))));
function sigs(
string memory sigStr
)
internal
pure
returns (bytes4)
{
return bytes4(keccak256(bytes(sigStr)));
}
/// @notice find an arbitrary storage slot given a function sig, input data, address of the contract and a value to check against
// slot complexity:
// if flat, will be bytes32(uint256(uint));
// if map, will be keccak256(abi.encode(key, uint(slot)));
// if deep map, will be keccak256(abi.encode(key1, keccak256(abi.encode(key0, uint(slot)))));
// if map struct, will be bytes32(uint256(keccak256(abi.encode(key1, keccak256(abi.encode(key0, uint(slot)))))) + structFieldDepth);
function find(
StdStorage storage self
)
internal
returns (uint256)
{
address who = self._target;
bytes4 fsig = self._sig;
uint256 field_depth = self._depth;
bytes32[] memory ins = self._keys;
// calldata to test against
if (self.finds[who][fsig][keccak256(abi.encodePacked(ins, field_depth))]) {
return self.slots[who][fsig][keccak256(abi.encodePacked(ins, field_depth))];
}
bytes memory cald = abi.encodePacked(fsig, flatten(ins));
vm_std_store.record();
bytes32 fdat;
{
(, bytes memory rdat) = who.staticcall(cald);
fdat = bytesToBytes32(rdat, 32*field_depth);
}
(bytes32[] memory reads, ) = vm_std_store.accesses(address(who));
if (reads.length == 1) {
bytes32 curr = vm_std_store.load(who, reads[0]);
if (curr == bytes32(0)) {
emit WARNING_UninitedSlot(who, uint256(reads[0]));
}
if (fdat != curr) {
require(false, "stdStorage find(StdStorage): Packed slot. This would cause dangerous overwriting and currently isn't supported.");
}
emit SlotFound(who, fsig, keccak256(abi.encodePacked(ins, field_depth)), uint256(reads[0]));
self.slots[who][fsig][keccak256(abi.encodePacked(ins, field_depth))] = uint256(reads[0]);
self.finds[who][fsig][keccak256(abi.encodePacked(ins, field_depth))] = true;
} else if (reads.length > 1) {
for (uint256 i = 0; i < reads.length; i++) {
bytes32 prev = vm_std_store.load(who, reads[i]);
if (prev == bytes32(0)) {
emit WARNING_UninitedSlot(who, uint256(reads[i]));
}
// store
vm_std_store.store(who, reads[i], bytes32(hex"1337"));
bool success;
bytes memory rdat;
{
(success, rdat) = who.staticcall(cald);
fdat = bytesToBytes32(rdat, 32*field_depth);
}
if (success && fdat == bytes32(hex"1337")) {
// we found which of the slots is the actual one
emit SlotFound(who, fsig, keccak256(abi.encodePacked(ins, field_depth)), uint256(reads[i]));
self.slots[who][fsig][keccak256(abi.encodePacked(ins, field_depth))] = uint256(reads[i]);
self.finds[who][fsig][keccak256(abi.encodePacked(ins, field_depth))] = true;
vm_std_store.store(who, reads[i], prev);
break;
}
vm_std_store.store(who, reads[i], prev);
}
} else {
require(false, "stdStorage find(StdStorage): No storage use detected for target.");
}
require(self.finds[who][fsig][keccak256(abi.encodePacked(ins, field_depth))], "stdStorage find(StdStorage): Slot(s) not found.");
delete self._target;
delete self._sig;
delete self._keys;
delete self._depth;
return self.slots[who][fsig][keccak256(abi.encodePacked(ins, field_depth))];
}
function target(StdStorage storage self, address _target) internal returns (StdStorage storage) {
self._target = _target;
return self;
}
function sig(StdStorage storage self, bytes4 _sig) internal returns (StdStorage storage) {
self._sig = _sig;
return self;
}
function sig(StdStorage storage self, string memory _sig) internal returns (StdStorage storage) {
self._sig = sigs(_sig);
return self;
}
function with_key(StdStorage storage self, address who) internal returns (StdStorage storage) {
self._keys.push(bytes32(uint256(uint160(who))));
return self;
}
function with_key(StdStorage storage self, uint256 amt) internal returns (StdStorage storage) {
self._keys.push(bytes32(amt));
return self;
}
function with_key(StdStorage storage self, bytes32 key) internal returns (StdStorage storage) {
self._keys.push(key);
return self;
}
function depth(StdStorage storage self, uint256 _depth) internal returns (StdStorage storage) {
self._depth = _depth;
return self;
}
function checked_write(StdStorage storage self, address who) internal {
checked_write(self, bytes32(uint256(uint160(who))));
}
function checked_write(StdStorage storage self, uint256 amt) internal {
checked_write(self, bytes32(amt));
}
function checked_write(StdStorage storage self, bool write) internal {
bytes32 t;
/// @solidity memory-safe-assembly
assembly {
t := write
}
checked_write(self, t);
}
function checked_write(
StdStorage storage self,
bytes32 set
) internal {
address who = self._target;
bytes4 fsig = self._sig;
uint256 field_depth = self._depth;
bytes32[] memory ins = self._keys;
bytes memory cald = abi.encodePacked(fsig, flatten(ins));
if (!self.finds[who][fsig][keccak256(abi.encodePacked(ins, field_depth))]) {
find(self);
}
bytes32 slot = bytes32(self.slots[who][fsig][keccak256(abi.encodePacked(ins, field_depth))]);
bytes32 fdat;
{
(, bytes memory rdat) = who.staticcall(cald);
fdat = bytesToBytes32(rdat, 32*field_depth);
}
bytes32 curr = vm_std_store.load(who, slot);
if (fdat != curr) {
require(false, "stdStorage find(StdStorage): Packed slot. This would cause dangerous overwriting and currently isn't supported.");
}
vm_std_store.store(who, slot, set);
delete self._target;
delete self._sig;
delete self._keys;
delete self._depth;
}
function read(StdStorage storage self) private returns (bytes memory) {
address t = self._target;
uint256 s = find(self);
return abi.encode(vm_std_store.load(t, bytes32(s)));
}
function read_bytes32(StdStorage storage self) internal returns (bytes32) {
return abi.decode(read(self), (bytes32));
}
function read_bool(StdStorage storage self) internal returns (bool) {
int256 v = read_int(self);
if (v == 0) return false;
if (v == 1) return true;
revert("stdStorage read_bool(StdStorage): Cannot decode. Make sure you are reading a bool.");
}
function read_address(StdStorage storage self) internal returns (address) {
return abi.decode(read(self), (address));
}
function read_uint(StdStorage storage self) internal returns (uint256) {
return abi.decode(read(self), (uint256));
}
function read_int(StdStorage storage self) internal returns (int256) {
return abi.decode(read(self), (int256));
}
function bytesToBytes32(bytes memory b, uint offset) public pure returns (bytes32) {
bytes32 out;
uint256 max = b.length > 32 ? 32 : b.length;
for (uint i = 0; i < max; i++) {
out |= bytes32(b[offset + i] & 0xFF) >> (i * 8);
}
return out;
}
function flatten(bytes32[] memory b) private pure returns (bytes memory)
{
bytes memory result = new bytes(b.length * 32);
for (uint256 i = 0; i < b.length; i++) {
bytes32 k = b[i];
/// @solidity memory-safe-assembly
assembly {
mstore(add(result, add(32, mul(32, i))), k)
}
}
return result;
}
}
/*//////////////////////////////////////////////////////////////////////////
STD-MATH
//////////////////////////////////////////////////////////////////////////*/
library stdMath {
int256 private constant INT256_MIN = -57896044618658097711785492504343953926634992332820282019728792003956564819968;
function abs(int256 a) internal pure returns (uint256) {
// Required or it will fail when `a = type(int256).min`
if (a == INT256_MIN)
return 57896044618658097711785492504343953926634992332820282019728792003956564819968;
return uint256(a > 0 ? a : -a);
}
function delta(uint256 a, uint256 b) internal pure returns (uint256) {
return a > b
? a - b
: b - a;
}
function delta(int256 a, int256 b) internal pure returns (uint256) {
// a and b are of the same sign
// this works thanks to two's complement, the left-most bit is the sign bit
if ((a ^ b) > -1) {
return delta(abs(a), abs(b));
}
// a and b are of opposite signs
return abs(a) + abs(b);
}
function percentDelta(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 absDelta = delta(a, b);
return absDelta * 1e18 / b;
}
function percentDelta(int256 a, int256 b) internal pure returns (uint256) {
uint256 absDelta = delta(a, b);
uint256 absB = abs(b);
return absDelta * 1e18 / absB;
}
}