Commit b3bfb3c7 authored by Mark Tyneway's avatar Mark Tyneway

resource-metering: change params

parent 4f96e19c
...@@ -397,17 +397,15 @@ RLPWriter_writeUint_Test:test_writeUint_smallint_succeeds() (gas: 7280) ...@@ -397,17 +397,15 @@ RLPWriter_writeUint_Test:test_writeUint_smallint_succeeds() (gas: 7280)
RLPWriter_writeUint_Test:test_writeUint_zero_succeeds() (gas: 7749) RLPWriter_writeUint_Test:test_writeUint_zero_succeeds() (gas: 7749)
ResolvedDelegateProxy_Test:test_fallback_addressManagerNotSet_reverts() (gas: 605906) ResolvedDelegateProxy_Test:test_fallback_addressManagerNotSet_reverts() (gas: 605906)
ResolvedDelegateProxy_Test:test_fallback_delegateCallBar_reverts() (gas: 24783) ResolvedDelegateProxy_Test:test_fallback_delegateCallBar_reverts() (gas: 24783)
ResourceMeteringCustom_Test:test_meter_generateArtifact_succeeds() (gas: 1042150946) ResourceMetering_Test:test_meter_initialBaseFee_succeeds() (gas: 7025)
ResourceMetering_Test:test_meter_initialBaseFee_succeeds() (gas: 7245)
ResourceMetering_Test:test_meter_initialResourceParams_succeeds() (gas: 8983) ResourceMetering_Test:test_meter_initialResourceParams_succeeds() (gas: 8983)
ResourceMetering_Test:test_meter_minBaseFeeLessThanMaxBaseFee_succeeds() (gas: 6420) ResourceMetering_Test:test_meter_minBaseFeeLessThanMaxBaseFee_succeeds() (gas: 6194)
ResourceMetering_Test:test_meter_updateNoGasDelta_succeeds() (gas: 2008226) ResourceMetering_Test:test_meter_updateNoGasDelta_succeeds() (gas: 4008242)
ResourceMetering_Test:test_meter_updateOneEmptyBlock_succeeds() (gas: 18645) ResourceMetering_Test:test_meter_updateOneEmptyBlock_succeeds() (gas: 18441)
ResourceMetering_Test:test_meter_updateParamsNoChange_succeeds() (gas: 14005) ResourceMetering_Test:test_meter_updateParamsNoChange_succeeds() (gas: 14005)
ResourceMetering_Test:test_meter_updateTenEmptyBlocks_succeeds() (gas: 21651) ResourceMetering_Test:test_meter_updateTenEmptyBlocks_succeeds() (gas: 21243)
ResourceMetering_Test:test_meter_updateTwoEmptyBlocks_succeeds() (gas: 21607) ResourceMetering_Test:test_meter_updateTwoEmptyBlocks_succeeds() (gas: 21199)
ResourceMetering_Test:test_meter_useMaxWithMaxBaseFee_succeeds() (gas: 29466331) ResourceMetering_Test:test_meter_useMax_succeeds() (gas: 20017420)
ResourceMetering_Test:test_meter_useMax_succeeds() (gas: 8017710)
ResourceMetering_Test:test_meter_useMoreThanMax_reverts() (gas: 16142) ResourceMetering_Test:test_meter_useMoreThanMax_reverts() (gas: 16142)
SafeCall_call_Test:test_callWithMinGas_noLeakageHigh_succeeds() (gas: 2075873614) SafeCall_call_Test:test_callWithMinGas_noLeakageHigh_succeeds() (gas: 2075873614)
SafeCall_call_Test:test_callWithMinGas_noLeakageLow_succeeds() (gas: 753665282) SafeCall_call_Test:test_callWithMinGas_noLeakageLow_succeeds() (gas: 753665282)
......
...@@ -29,13 +29,14 @@ abstract contract ResourceMetering is Initializable { ...@@ -29,13 +29,14 @@ abstract contract ResourceMetering is Initializable {
/** /**
* @notice Maximum amount of the resource that can be used within this block. * @notice Maximum amount of the resource that can be used within this block.
* This value cannot be larger than the L2 block gas limit.
*/ */
int256 public constant MAX_RESOURCE_LIMIT = 8_000_000; int256 public constant MAX_RESOURCE_LIMIT = 20_000_000;
/** /**
* @notice Along with the resource limit, determines the target resource limit. * @notice Along with the resource limit, determines the target resource limit.
*/ */
int256 public constant ELASTICITY_MULTIPLIER = 4; int256 public constant ELASTICITY_MULTIPLIER = 5;
/** /**
* @notice Target amount of the resource that should be used within this block. * @notice Target amount of the resource that should be used within this block.
...@@ -50,22 +51,21 @@ abstract contract ResourceMetering is Initializable { ...@@ -50,22 +51,21 @@ abstract contract ResourceMetering is Initializable {
/** /**
* @notice Minimum base fee value, cannot go lower than this. * @notice Minimum base fee value, cannot go lower than this.
*/ */
int256 public constant MINIMUM_BASE_FEE = 10_000; int256 public constant MINIMUM_BASE_FEE = 1 gwei;
/** /**
* @notice Maximum base fee value, cannot go higher than this. * @notice Maximum base fee value, cannot go higher than this.
* This value must be small enough to allow a user to request the * It is possible for the MAXIMUM_BASE_FEE to raise to a value
* MAX_RESOURCE_LIMIT when the base fee is at its max value. * that is so large it will consume the entire gas limit of
* Setting this value too large can result in more gas being * an L1 block.
* consumed than the L1 block gas limit.
*/ */
int256 public constant MAXIMUM_BASE_FEE = int256(uint256((type(uint32).max / 7) * 6)); int256 public constant MAXIMUM_BASE_FEE = int256(uint256((type(uint128).max)));
/** /**
* @notice Initial base fee value. This value must be smaller than the * @notice Initial base fee value. This value must be smaller than the
* MAXIMUM_BASE_FEE. * MAXIMUM_BASE_FEE.
*/ */
uint128 public constant INITIAL_BASE_FEE = 1_000_000_000; uint128 public constant INITIAL_BASE_FEE = 1 gwei;
/** /**
* @notice EIP-1559 style gas parameters. * @notice EIP-1559 style gas parameters.
...@@ -89,10 +89,12 @@ abstract contract ResourceMetering is Initializable { ...@@ -89,10 +89,12 @@ abstract contract ResourceMetering is Initializable {
// Run the underlying function. // Run the underlying function.
_; _;
// Run the metering function.
_metered(_amount, initialGas); _metered(_amount, initialGas);
} }
/** /**
* @notice An internal function that holds all of the logic for metering a resource. * @notice An internal function that holds all of the logic for metering a resource.
* *
* @param _amount Amount of the resource requested. * @param _amount Amount of the resource requested.
......
...@@ -32,7 +32,7 @@ contract SetPrevBaseFee_Test is Portal_Initializer { ...@@ -32,7 +32,7 @@ contract SetPrevBaseFee_Test is Portal_Initializer {
// In order to achieve this we make no assertions, and handle everything else in the setUp() // In order to achieve this we make no assertions, and handle everything else in the setUp()
// function. // function.
contract GasBenchMark_OptimismPortal is Portal_Initializer { contract GasBenchMark_OptimismPortal is Portal_Initializer {
uint128 INITIAL_BASE_FEE; uint128 internal INITIAL_BASE_FEE;
// Reusable default values for a test withdrawal // Reusable default values for a test withdrawal
Types.WithdrawalTransaction _defaultTx; Types.WithdrawalTransaction _defaultTx;
...@@ -124,7 +124,7 @@ contract GasBenchMark_OptimismPortal is Portal_Initializer { ...@@ -124,7 +124,7 @@ contract GasBenchMark_OptimismPortal is Portal_Initializer {
} }
contract GasBenchMark_L1CrossDomainMessenger is Messenger_Initializer { contract GasBenchMark_L1CrossDomainMessenger is Messenger_Initializer {
uint128 INITIAL_BASE_FEE; uint128 internal INITIAL_BASE_FEE;
function setUp() public virtual override { function setUp() public virtual override {
super.setUp(); super.setUp();
...@@ -153,7 +153,7 @@ contract GasBenchMark_L1CrossDomainMessenger is Messenger_Initializer { ...@@ -153,7 +153,7 @@ contract GasBenchMark_L1CrossDomainMessenger is Messenger_Initializer {
} }
contract GasBenchMark_L1StandardBridge_Deposit is Bridge_Initializer { contract GasBenchMark_L1StandardBridge_Deposit is Bridge_Initializer {
uint128 INITIAL_BASE_FEE; uint128 internal INITIAL_BASE_FEE;
function setUp() public virtual override { function setUp() public virtual override {
super.setUp(); super.setUp();
......
...@@ -46,8 +46,8 @@ contract ResourceMetering_Test is Test { ...@@ -46,8 +46,8 @@ contract ResourceMetering_Test is Test {
uint256 max = uint256(meter.MAXIMUM_BASE_FEE()); uint256 max = uint256(meter.MAXIMUM_BASE_FEE());
uint256 min = uint256(meter.MINIMUM_BASE_FEE()); uint256 min = uint256(meter.MINIMUM_BASE_FEE());
uint256 initial = uint256(meter.INITIAL_BASE_FEE()); uint256 initial = uint256(meter.INITIAL_BASE_FEE());
assertTrue(max > initial); assertTrue(max >= initial);
assertTrue(min < initial); assertTrue(min <= initial);
} }
/** /**
...@@ -83,8 +83,7 @@ contract ResourceMetering_Test is Test { ...@@ -83,8 +83,7 @@ contract ResourceMetering_Test is Test {
meter.use(0); meter.use(0);
(uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = meter.params(); (uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = meter.params();
// Base fee decreases by 12.5% assertEq(prevBaseFee, 1 gwei);
assertEq(prevBaseFee, 875000000);
assertEq(prevBoughtGas, 0); assertEq(prevBoughtGas, 0);
assertEq(prevBlockNum, initialBlockNum + 1); assertEq(prevBlockNum, initialBlockNum + 1);
} }
...@@ -94,7 +93,7 @@ contract ResourceMetering_Test is Test { ...@@ -94,7 +93,7 @@ contract ResourceMetering_Test is Test {
meter.use(0); meter.use(0);
(uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = meter.params(); (uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = meter.params();
assertEq(prevBaseFee, 765624999); assertEq(prevBaseFee, 1 gwei);
assertEq(prevBoughtGas, 0); assertEq(prevBoughtGas, 0);
assertEq(prevBlockNum, initialBlockNum + 2); assertEq(prevBlockNum, initialBlockNum + 2);
} }
...@@ -104,7 +103,7 @@ contract ResourceMetering_Test is Test { ...@@ -104,7 +103,7 @@ contract ResourceMetering_Test is Test {
meter.use(0); meter.use(0);
(uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = meter.params(); (uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = meter.params();
assertEq(prevBaseFee, 263075576); assertEq(prevBaseFee, 1 gwei);
assertEq(prevBoughtGas, 0); assertEq(prevBoughtGas, 0);
assertEq(prevBlockNum, initialBlockNum + 10); assertEq(prevBlockNum, initialBlockNum + 10);
} }
...@@ -130,8 +129,7 @@ contract ResourceMetering_Test is Test { ...@@ -130,8 +129,7 @@ contract ResourceMetering_Test is Test {
vm.roll(initialBlockNum + 1); vm.roll(initialBlockNum + 1);
meter.use(0); meter.use(0);
(uint128 postBaseFee, , ) = meter.params(); (uint128 postBaseFee, , ) = meter.params();
// Base fee increases by 1/8 the difference assertEq(postBaseFee, 1500000000);
assertEq(postBaseFee, 1375000000);
} }
function test_meter_useMoreThanMax_reverts() external { function test_meter_useMoreThanMax_reverts() external {
...@@ -141,36 +139,6 @@ contract ResourceMetering_Test is Test { ...@@ -141,36 +139,6 @@ contract ResourceMetering_Test is Test {
meter.use(target * elasticity + 1); meter.use(target * elasticity + 1);
} }
/**
* @notice The max resource limit should be able to be used when the L1
* deposit base fee is at its max value. This previously would
* revert because prevBaseFee is a uint128 and checked math when
* multiplying against a uint64 _amount can result in an overflow
* even though its assigning to a uint256. The values MUST be casted
* to uint256 when doing the multiplication to prevent overflows.
* The function is called with the L1 block gas limit to ensure that
* the MAX_RESOURCE_LIMIT can be consumed at the MAXIMUM_BASE_FEE.
*/
function test_meter_useMaxWithMaxBaseFee_succeeds() external {
uint128 _prevBaseFee = uint128(uint256(meter.MAXIMUM_BASE_FEE()));
uint64 _prevBoughtGas = 0;
uint64 _prevBlockNum = uint64(block.number);
meter.set({
_prevBaseFee: _prevBaseFee,
_prevBoughtGas: _prevBoughtGas,
_prevBlockNum: _prevBlockNum
});
(uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = meter.params();
assertEq(prevBaseFee, _prevBaseFee);
assertEq(prevBoughtGas, _prevBoughtGas);
assertEq(prevBlockNum, _prevBlockNum);
uint64 gasRequested = uint64(uint256(meter.MAX_RESOURCE_LIMIT()));
meter.use{ gas: 30_000_000 }(gasRequested);
}
// Demonstrates that the resource metering arithmetic can tolerate very large gaps between // Demonstrates that the resource metering arithmetic can tolerate very large gaps between
// deposits. // deposits.
function testFuzz_meter_largeBlockDiff_succeeds(uint64 _amount, uint256 _blockDiff) external { function testFuzz_meter_largeBlockDiff_succeeds(uint64 _amount, uint256 _blockDiff) external {
...@@ -187,11 +155,11 @@ contract ResourceMetering_Test is Test { ...@@ -187,11 +155,11 @@ contract ResourceMetering_Test is Test {
} }
/** /**
* @title MeterUserCustom * @title CustomMeterUser
* @notice A simple wrapper around `ResourceMetering` that allows the initial * @notice A simple wrapper around `ResourceMetering` that allows the initial
* params to be set in the constructor. * params to be set in the constructor.
*/ */
contract MeterUserCustom is ResourceMetering { contract CustomMeterUser is ResourceMetering {
uint256 public startGas; uint256 public startGas;
uint256 public endGas; uint256 public endGas;
...@@ -215,17 +183,20 @@ contract MeterUserCustom is ResourceMetering { ...@@ -215,17 +183,20 @@ contract MeterUserCustom is ResourceMetering {
} }
/** /**
* @title ResourceMeteringCustom_Test * @title ArtifactResourceMetering_Test
* @notice A table test that sets the state of the ResourceParams and then requests * @notice A table test that sets the state of the ResourceParams and then requests
* various amounts of gas. This test ensures that a wide range of values * various amounts of gas. This test ensures that a wide range of values
* can safely be used with the `ResourceMetering` contract. * can safely be used with the `ResourceMetering` contract.
* It also writes a CSV file to disk that includes useful information * It also writes a CSV file to disk that includes useful information
* about how much gas is used and how expensive it is in USD terms to * about how much gas is used and how expensive it is in USD terms to
* purchase the deposit gas. * purchase the deposit gas.
* This contract is designed to have only a single test.
*/ */
contract ResourceMeteringCustom_Test is Test { contract ArtifactResourceMetering_Test is Test {
MeterUser internal base; uint128 internal minimumBaseFee;
uint128 internal maximumBaseFee;
uint64 internal maxResourceLimit;
uint64 internal targetResourceLimit;
string internal outfile; string internal outfile;
// keccak256(abi.encodeWithSignature("Error(string)", "ResourceMetering: cannot buy more gas than available gas limit")) // keccak256(abi.encodeWithSignature("Error(string)", "ResourceMetering: cannot buy more gas than available gas limit"))
...@@ -234,23 +205,26 @@ contract ResourceMeteringCustom_Test is Test { ...@@ -234,23 +205,26 @@ contract ResourceMeteringCustom_Test is Test {
// keccak256(abi.encodeWithSignature("Panic(uint256)", 0x11)) // keccak256(abi.encodeWithSignature("Panic(uint256)", 0x11))
bytes32 internal overflowErr = bytes32 internal overflowErr =
0x1ca389f2c8264faa4377de9ce8e14d6263ef29c68044a9272d405761bab2db27; 0x1ca389f2c8264faa4377de9ce8e14d6263ef29c68044a9272d405761bab2db27;
// keccak256(hex"")
bytes32 internal emptyReturnData =
0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
/** /**
* @notice Sets the initial block number to something sane for the * @notice Sets up the tests by getting constants from the ResourceMetering
* deployment of MeterUser. Delete the CSV file if it exists * contract.
* then write the first line of the CSV.
*/ */
function setUp() public { function setUp() public {
vm.roll(1_000_000); vm.roll(1_000_000);
base = new MeterUser(); MeterUser base = new MeterUser();
outfile = string.concat(vm.projectRoot(), "/.resource-metering.csv"); minimumBaseFee = uint128(uint256(base.MINIMUM_BASE_FEE()));
maximumBaseFee = uint128(uint256(base.MAXIMUM_BASE_FEE()));
maxResourceLimit = uint64(uint256(base.MAX_RESOURCE_LIMIT()));
targetResourceLimit = uint64(uint256(base.TARGET_RESOURCE_LIMIT()));
outfile = string.concat(vm.projectRoot(), "/.resource-metering.csv");
try vm.removeFile(outfile) {} catch {} try vm.removeFile(outfile) {} catch {}
vm.writeLine(
outfile,
"prevBaseFee,prevBoughtGas,prevBlockNumDiff,l1BaseFee,requestedGas,gasConsumed,ethPrice,usdCost,success"
);
} }
/** /**
...@@ -259,19 +233,22 @@ contract ResourceMeteringCustom_Test is Test { ...@@ -259,19 +233,22 @@ contract ResourceMeteringCustom_Test is Test {
* gas, it can take very long to execute. * gas, it can take very long to execute.
*/ */
function test_meter_generateArtifact_succeeds() external { function test_meter_generateArtifact_succeeds() external {
vm.writeLine(
outfile,
"prevBaseFee,prevBoughtGas,prevBlockNumDiff,l1BaseFee,requestedGas,gasConsumed,ethPrice,usdCost,success"
);
// prevBaseFee value in ResourceParams // prevBaseFee value in ResourceParams
uint128[] memory prevBaseFees = new uint128[](5); uint128[] memory prevBaseFees = new uint128[](5);
prevBaseFees[0] = uint128(uint256(base.MAXIMUM_BASE_FEE())); prevBaseFees[0] = minimumBaseFee;
prevBaseFees[1] = uint128(uint256(base.MINIMUM_BASE_FEE())); prevBaseFees[1] = maximumBaseFee;
prevBaseFees[2] = uint128(uint256(base.INITIAL_BASE_FEE())); prevBaseFees[2] = uint128(50 gwei);
prevBaseFees[3] = uint128(100_000); prevBaseFees[3] = uint128(100 gwei);
prevBaseFees[4] = uint128(500_000); prevBaseFees[4] = uint128(200 gwei);
// prevBoughtGas value in ResourceParams // prevBoughtGas value in ResourceParams
uint64[] memory prevBoughtGases = new uint64[](3); uint64[] memory prevBoughtGases = new uint64[](1);
prevBoughtGases[0] = uint64(uint256(base.MAX_RESOURCE_LIMIT())); prevBoughtGases[0] = uint64(0);
prevBoughtGases[1] = uint64(uint256(base.TARGET_RESOURCE_LIMIT()));
prevBoughtGases[2] = uint64(0);
// prevBlockNum diff, simulates blocks with no deposits when non zero // prevBlockNum diff, simulates blocks with no deposits when non zero
uint64[] memory prevBlockNumDiffs = new uint64[](2); uint64[] memory prevBlockNumDiffs = new uint64[](2);
...@@ -280,8 +257,8 @@ contract ResourceMeteringCustom_Test is Test { ...@@ -280,8 +257,8 @@ contract ResourceMeteringCustom_Test is Test {
// The amount of L2 gas that a user requests // The amount of L2 gas that a user requests
uint64[] memory requestedGases = new uint64[](3); uint64[] memory requestedGases = new uint64[](3);
requestedGases[0] = uint64(uint256(base.MAX_RESOURCE_LIMIT())); requestedGases[0] = maxResourceLimit;
requestedGases[1] = uint64(uint256(base.TARGET_RESOURCE_LIMIT())); requestedGases[1] = targetResourceLimit;
requestedGases[2] = uint64(100_000); requestedGases[2] = uint64(100_000);
// The L1 base fee // The L1 base fee
...@@ -315,7 +292,7 @@ contract ResourceMeteringCustom_Test is Test { ...@@ -315,7 +292,7 @@ contract ResourceMeteringCustom_Test is Test {
vm.fee(l1BaseFee); vm.fee(l1BaseFee);
MeterUserCustom meter = new MeterUserCustom({ CustomMeterUser meter = new CustomMeterUser({
_prevBaseFee: prevBaseFee, _prevBaseFee: prevBaseFee,
_prevBoughtGas: prevBoughtGas, _prevBoughtGas: prevBoughtGas,
_prevBlockNum: uint64(block.number) _prevBlockNum: uint64(block.number)
...@@ -323,6 +300,8 @@ contract ResourceMeteringCustom_Test is Test { ...@@ -323,6 +300,8 @@ contract ResourceMeteringCustom_Test is Test {
vm.roll(block.number + prevBlockNumDiff); vm.roll(block.number + prevBlockNumDiff);
// Call the metering code and catch the various
// types of errors.
uint256 gasConsumed = 0; uint256 gasConsumed = 0;
try meter.use{ gas: 30_000_000 }(requestedGas) returns ( try meter.use{ gas: 30_000_000 }(requestedGas) returns (
uint256 _gasConsumed uint256 _gasConsumed
...@@ -334,13 +313,14 @@ contract ResourceMeteringCustom_Test is Test { ...@@ -334,13 +313,14 @@ contract ResourceMeteringCustom_Test is Test {
result = "ResourceMetering: cannot buy more gas than available gas limit"; result = "ResourceMetering: cannot buy more gas than available gas limit";
} else if (hash == overflowErr) { } else if (hash == overflowErr) {
result = "arithmetic overflow/underflow"; result = "arithmetic overflow/underflow";
} else if (hash == emptyReturnData) {
result = "out of gas";
} else { } else {
result = "UNKNOWN ERROR"; result = "UNKNOWN ERROR";
} }
} }
// Compute the USD cost of the gas used, don't // Compute the USD cost of the gas used
// worry too much about loss of precison under $1
uint256 usdCost = (gasConsumed * l1BaseFee * ethPrice) / 1 ether; uint256 usdCost = (gasConsumed * l1BaseFee * ethPrice) / 1 ether;
vm.writeLine( vm.writeLine(
......
...@@ -20,7 +20,7 @@ fuzz_runs = 16 ...@@ -20,7 +20,7 @@ fuzz_runs = 16
no_match_contract = 'EchidnaFuzz' no_match_contract = 'EchidnaFuzz'
fs_permissions = [ fs_permissions = [
{ 'access'='read-write', 'path'='./' }, { 'access'='read-write', 'path'='./.resource-metering.csv' },
] ]
[profile.ci] [profile.ci]
......
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
"test": "yarn build:differential && yarn build:fuzz && forge test", "test": "yarn build:differential && yarn build:fuzz && forge test",
"coverage": "yarn build:differential && yarn build:fuzz && forge coverage", "coverage": "yarn build:differential && yarn build:fuzz && forge coverage",
"coverage:lcov": "yarn build:differential && yarn build:fuzz && forge coverage --report lcov", "coverage:lcov": "yarn build:differential && yarn build:fuzz && forge coverage --report lcov",
"gas-snapshot": "yarn build:differential && yarn build:fuzz && forge snapshot --no-match-test 'testDiff|testFuzz|invariant|ResourceMeteringCustom'", "gas-snapshot": "yarn build:differential && yarn build:fuzz && forge snapshot --no-match-test 'testDiff|testFuzz|invariant|generateArtifact'",
"storage-snapshot": "./scripts/storage-snapshot.sh", "storage-snapshot": "./scripts/storage-snapshot.sh",
"validate-spacers": "hardhat compile && hardhat validate-spacers", "validate-spacers": "hardhat compile && hardhat validate-spacers",
"slither": "./scripts/slither.sh", "slither": "./scripts/slither.sh",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment