mirror of
https://github.com/lens-protocol/core.git
synced 2026-04-22 03:02:03 -04:00
1139 lines
37 KiB
Solidity
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;
|
|
}
|
|
}
|