Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
N
nebula
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
exchain
nebula
Commits
01fbb00a
Commit
01fbb00a
authored
Jun 22, 2023
by
Andreas Bigger
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
More styling fixes
parent
26f536dc
Changes
3
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
153 additions
and
132 deletions
+153
-132
OptimismPortal.t.sol
...ges/contracts-bedrock/contracts/test/OptimismPortal.t.sol
+87
-89
ResourceMetering.t.sol
...s/contracts-bedrock/contracts/test/ResourceMetering.t.sol
+38
-41
SystemConfig.t.sol
packages/contracts-bedrock/contracts/test/SystemConfig.t.sol
+28
-2
No files found.
packages/contracts-bedrock/contracts/test/OptimismPortal.t.sol
View file @
01fbb00a
This diff is collapsed.
Click to expand it.
packages/contracts-bedrock/contracts/test/ResourceMetering.t.sol
View file @
01fbb00a
// SPDX-License-Identifier: MIT
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
pragma solidity 0.8.15;
// Testing utilities
import { Test } from "forge-std/Test.sol";
import { Test } from "forge-std/Test.sol";
import { ResourceMetering } from "../L1/ResourceMetering.sol";
import { Proxy } from "../universal/Proxy.sol";
// Libraries
import { Constants } from "../libraries/Constants.sol";
import { Constants } from "../libraries/Constants.sol";
// Target contract dependencies
import { Proxy } from "../universal/Proxy.sol";
// Target contract
import { ResourceMetering } from "../L1/ResourceMetering.sol";
contract MeterUser is ResourceMetering {
contract MeterUser is ResourceMetering {
ResourceMetering.ResourceConfig public innerConfig;
ResourceMetering.ResourceConfig public innerConfig;
...
@@ -50,20 +57,20 @@ contract MeterUser is ResourceMetering {
...
@@ -50,20 +57,20 @@ contract MeterUser is ResourceMetering {
}
}
}
}
/**
/// @title ResourceMetering_Test
* @title ResourceConfig
/// @dev Tests are based on the default config values.
* @notice The tests are based on the default config values. It is expected that
/// It is expected that these config values are used in production.
* the config values used in these tests are ran in production.
*/
contract ResourceMetering_Test is Test {
contract ResourceMetering_Test is Test {
MeterUser internal meter;
MeterUser internal meter;
uint64 initialBlockNum;
uint64 initialBlockNum;
/// @dev Sets up the test contract.
function setUp() public {
function setUp() public {
meter = new MeterUser();
meter = new MeterUser();
initialBlockNum = uint64(block.number);
initialBlockNum = uint64(block.number);
}
}
/// @dev Tests that the initial resource params are set correctly.
function test_meter_initialResourceParams_succeeds() external {
function test_meter_initialResourceParams_succeeds() external {
(uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = meter.params();
(uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = meter.params();
ResourceMetering.ResourceConfig memory rcfg = meter.resourceConfig();
ResourceMetering.ResourceConfig memory rcfg = meter.resourceConfig();
...
@@ -73,6 +80,7 @@ contract ResourceMetering_Test is Test {
...
@@ -73,6 +80,7 @@ contract ResourceMetering_Test is Test {
assertEq(prevBlockNum, initialBlockNum);
assertEq(prevBlockNum, initialBlockNum);
}
}
/// @dev Tests that updating the resource params to the same values works correctly.
function test_meter_updateParamsNoChange_succeeds() external {
function test_meter_updateParamsNoChange_succeeds() external {
meter.use(0); // equivalent to just updating the base fee and block number
meter.use(0); // equivalent to just updating the base fee and block number
(uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = meter.params();
(uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = meter.params();
...
@@ -84,6 +92,7 @@ contract ResourceMetering_Test is Test {
...
@@ -84,6 +92,7 @@ contract ResourceMetering_Test is Test {
assertEq(postBlockNum, prevBlockNum);
assertEq(postBlockNum, prevBlockNum);
}
}
/// @dev Tests that updating the initial block number sets the meter params correctly.
function test_meter_updateOneEmptyBlock_succeeds() external {
function test_meter_updateOneEmptyBlock_succeeds() external {
vm.roll(initialBlockNum + 1);
vm.roll(initialBlockNum + 1);
meter.use(0);
meter.use(0);
...
@@ -94,6 +103,7 @@ contract ResourceMetering_Test is Test {
...
@@ -94,6 +103,7 @@ contract ResourceMetering_Test is Test {
assertEq(prevBlockNum, initialBlockNum + 1);
assertEq(prevBlockNum, initialBlockNum + 1);
}
}
/// @dev Tests that updating the initial block number sets the meter params correctly.
function test_meter_updateTwoEmptyBlocks_succeeds() external {
function test_meter_updateTwoEmptyBlocks_succeeds() external {
vm.roll(initialBlockNum + 2);
vm.roll(initialBlockNum + 2);
meter.use(0);
meter.use(0);
...
@@ -104,6 +114,7 @@ contract ResourceMetering_Test is Test {
...
@@ -104,6 +114,7 @@ contract ResourceMetering_Test is Test {
assertEq(prevBlockNum, initialBlockNum + 2);
assertEq(prevBlockNum, initialBlockNum + 2);
}
}
/// @dev Tests that updating the initial block number sets the meter params correctly.
function test_meter_updateTenEmptyBlocks_succeeds() external {
function test_meter_updateTenEmptyBlocks_succeeds() external {
vm.roll(initialBlockNum + 10);
vm.roll(initialBlockNum + 10);
meter.use(0);
meter.use(0);
...
@@ -114,6 +125,7 @@ contract ResourceMetering_Test is Test {
...
@@ -114,6 +125,7 @@ contract ResourceMetering_Test is Test {
assertEq(prevBlockNum, initialBlockNum + 10);
assertEq(prevBlockNum, initialBlockNum + 10);
}
}
/// @dev Tests that updating the gas delta sets the meter params correctly.
function test_meter_updateNoGasDelta_succeeds() external {
function test_meter_updateNoGasDelta_succeeds() external {
ResourceMetering.ResourceConfig memory rcfg = meter.resourceConfig();
ResourceMetering.ResourceConfig memory rcfg = meter.resourceConfig();
uint256 target = uint256(rcfg.maxResourceLimit) / uint256(rcfg.elasticityMultiplier);
uint256 target = uint256(rcfg.maxResourceLimit) / uint256(rcfg.elasticityMultiplier);
...
@@ -125,6 +137,7 @@ contract ResourceMetering_Test is Test {
...
@@ -125,6 +137,7 @@ contract ResourceMetering_Test is Test {
assertEq(prevBlockNum, initialBlockNum);
assertEq(prevBlockNum, initialBlockNum);
}
}
/// @dev Tests that the meter params are set correctly for the maximum gas delta.
function test_meter_useMax_succeeds() external {
function test_meter_useMax_succeeds() external {
ResourceMetering.ResourceConfig memory rcfg = meter.resourceConfig();
ResourceMetering.ResourceConfig memory rcfg = meter.resourceConfig();
uint64 target = uint64(rcfg.maxResourceLimit) / uint64(rcfg.elasticityMultiplier);
uint64 target = uint64(rcfg.maxResourceLimit) / uint64(rcfg.elasticityMultiplier);
...
@@ -141,15 +154,9 @@ contract ResourceMetering_Test is Test {
...
@@ -141,15 +154,9 @@ contract ResourceMetering_Test is Test {
assertEq(postBaseFee, 2125000000);
assertEq(postBaseFee, 2125000000);
}
}
/**
/// @dev Tests that the metered modifier reverts if the baseFeeMaxChangeDenominator is set to 1.
* @notice This tests that the metered modifier reverts if
/// Since the metered modifier internally calls solmate's powWad function, it will revert
* the ResourceConfig baseFeeMaxChangeDenominator
/// with the error string "UNDEFINED" since the first parameter will be computed as 0.
* is set to 1.
* Since the metered modifier internally calls
* solmate's powWad function, it will revert
* with the error string "UNDEFINED" since the
* first parameter will be computed as 0.
*/
function test_meter_denominatorEq1_reverts() external {
function test_meter_denominatorEq1_reverts() external {
ResourceMetering.ResourceConfig memory rcfg = meter.resourceConfig();
ResourceMetering.ResourceConfig memory rcfg = meter.resourceConfig();
uint64 target = uint64(rcfg.maxResourceLimit) / uint64(rcfg.elasticityMultiplier);
uint64 target = uint64(rcfg.maxResourceLimit) / uint64(rcfg.elasticityMultiplier);
...
@@ -167,6 +174,7 @@ contract ResourceMetering_Test is Test {
...
@@ -167,6 +174,7 @@ contract ResourceMetering_Test is Test {
meter.use(0);
meter.use(0);
}
}
/// @dev Tests that the metered modifier reverts if the value is greater than allowed.
function test_meter_useMoreThanMax_reverts() external {
function test_meter_useMoreThanMax_reverts() external {
ResourceMetering.ResourceConfig memory rcfg = meter.resourceConfig();
ResourceMetering.ResourceConfig memory rcfg = meter.resourceConfig();
uint64 target = uint64(rcfg.maxResourceLimit) / uint64(rcfg.elasticityMultiplier);
uint64 target = uint64(rcfg.maxResourceLimit) / uint64(rcfg.elasticityMultiplier);
...
@@ -176,8 +184,7 @@ contract ResourceMetering_Test is Test {
...
@@ -176,8 +184,7 @@ contract ResourceMetering_Test is Test {
meter.use(target * elasticityMultiplier + 1);
meter.use(target * elasticityMultiplier + 1);
}
}
// Demonstrates that the resource metering arithmetic can tolerate very large gaps between
/// @dev Tests that resource metering can handle 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 {
// This test fails if the following line is commented out.
// This test fails if the following line is commented out.
// At 12 seconds per block, this number is effectively unreachable.
// At 12 seconds per block, this number is effectively unreachable.
...
@@ -193,11 +200,9 @@ contract ResourceMetering_Test is Test {
...
@@ -193,11 +200,9 @@ contract ResourceMetering_Test is Test {
}
}
}
}
/**
/// @title CustomMeterUser
* @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 CustomMeterUser is ResourceMetering {
contract CustomMeterUser is ResourceMetering {
uint256 public startGas;
uint256 public startGas;
uint256 public endGas;
uint256 public endGas;
...
@@ -230,15 +235,13 @@ contract CustomMeterUser is ResourceMetering {
...
@@ -230,15 +235,13 @@ contract CustomMeterUser is ResourceMetering {
}
}
}
}
/**
/// @title ArtifactResourceMetering_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.
*/
contract ArtifactResourceMetering_Test is Test {
contract ArtifactResourceMetering_Test is Test {
uint128 internal minimumBaseFee;
uint128 internal minimumBaseFee;
uint128 internal maximumBaseFee;
uint128 internal maximumBaseFee;
...
@@ -257,10 +260,7 @@ contract ArtifactResourceMetering_Test is Test {
...
@@ -257,10 +260,7 @@ contract ArtifactResourceMetering_Test is Test {
bytes32 internal emptyReturnData =
bytes32 internal emptyReturnData =
0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
/**
/// @dev Sets up the tests with constants from the ResourceMetering contract.
* @notice Sets up the tests by getting constants from the ResourceMetering
* contract.
*/
function setUp() public {
function setUp() public {
vm.roll(1_000_000);
vm.roll(1_000_000);
...
@@ -275,11 +275,8 @@ contract ArtifactResourceMetering_Test is Test {
...
@@ -275,11 +275,8 @@ contract ArtifactResourceMetering_Test is Test {
try vm.removeFile(outfile) {} catch {}
try vm.removeFile(outfile) {} catch {}
}
}
/**
/// @dev Generates a CSV file. No more than the L1 block gas limit should
* @notice Generate a CSV file. The call to `meter` should be called with at
/// be supplied to the `meter` function to avoid long execution time.
* most the L1 block gas limit. Without specifying the amount of
* gas, it can take very long to execute.
*/
function test_meter_generateArtifact_succeeds() external {
function test_meter_generateArtifact_succeeds() external {
vm.writeLine(
vm.writeLine(
outfile,
outfile,
...
...
packages/contracts-bedrock/contracts/test/SystemConfig.t.sol
View file @
01fbb00a
// SPDX-License-Identifier: MIT
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
pragma solidity 0.8.15;
// Testing utilities
import { CommonTest } from "./CommonTest.t.sol";
import { CommonTest } from "./CommonTest.t.sol";
import { SystemConfig } from "../L1/SystemConfig.sol";
import { ResourceMetering } from "../L1/ResourceMetering.sol";
// Libraries
import { Constants } from "../libraries/Constants.sol";
import { Constants } from "../libraries/Constants.sol";
// Target contract dependencies
import { ResourceMetering } from "../L1/ResourceMetering.sol";
// Target contract
import { SystemConfig } from "../L1/SystemConfig.sol";
contract SystemConfig_Init is CommonTest {
contract SystemConfig_Init is CommonTest {
SystemConfig sysConf;
SystemConfig sysConf;
...
@@ -34,6 +41,8 @@ contract SystemConfig_Init is CommonTest {
...
@@ -34,6 +41,8 @@ contract SystemConfig_Init is CommonTest {
}
}
contract SystemConfig_Initialize_TestFail is SystemConfig_Init {
contract SystemConfig_Initialize_TestFail is SystemConfig_Init {
/// @dev Tests that initialization reverts if the gas limit is too low.
function test_initialize_lowGasLimit_reverts() external {
function test_initialize_lowGasLimit_reverts() external {
uint64 minimumGasLimit = sysConf.minimumGasLimit();
uint64 minimumGasLimit = sysConf.minimumGasLimit();
...
@@ -60,32 +69,40 @@ contract SystemConfig_Initialize_TestFail is SystemConfig_Init {
...
@@ -60,32 +69,40 @@ contract SystemConfig_Initialize_TestFail is SystemConfig_Init {
}
}
contract SystemConfig_Setters_TestFail is SystemConfig_Init {
contract SystemConfig_Setters_TestFail is SystemConfig_Init {
/// @dev Tests that `setBatcherHash` reverts if the caller is not the owner.
function test_setBatcherHash_notOwner_reverts() external {
function test_setBatcherHash_notOwner_reverts() external {
vm.expectRevert("Ownable: caller is not the owner");
vm.expectRevert("Ownable: caller is not the owner");
sysConf.setBatcherHash(bytes32(hex""));
sysConf.setBatcherHash(bytes32(hex""));
}
}
/// @dev Tests that `setGasConfig` reverts if the caller is not the owner.
function test_setGasConfig_notOwner_reverts() external {
function test_setGasConfig_notOwner_reverts() external {
vm.expectRevert("Ownable: caller is not the owner");
vm.expectRevert("Ownable: caller is not the owner");
sysConf.setGasConfig(0, 0);
sysConf.setGasConfig(0, 0);
}
}
/// @dev Tests that `setGasLimit` reverts if the caller is not the owner.
function test_setGasLimit_notOwner_reverts() external {
function test_setGasLimit_notOwner_reverts() external {
vm.expectRevert("Ownable: caller is not the owner");
vm.expectRevert("Ownable: caller is not the owner");
sysConf.setGasLimit(0);
sysConf.setGasLimit(0);
}
}
/// @dev Tests that `setUnsafeBlockSigner` reverts if the caller is not the owner.
function test_setUnsafeBlockSigner_notOwner_reverts() external {
function test_setUnsafeBlockSigner_notOwner_reverts() external {
vm.expectRevert("Ownable: caller is not the owner");
vm.expectRevert("Ownable: caller is not the owner");
sysConf.setUnsafeBlockSigner(address(0x20));
sysConf.setUnsafeBlockSigner(address(0x20));
}
}
/// @dev Tests that `setResourceConfig` reverts if the caller is not the owner.
function test_setResourceConfig_notOwner_reverts() external {
function test_setResourceConfig_notOwner_reverts() external {
ResourceMetering.ResourceConfig memory config = Constants.DEFAULT_RESOURCE_CONFIG();
ResourceMetering.ResourceConfig memory config = Constants.DEFAULT_RESOURCE_CONFIG();
vm.expectRevert("Ownable: caller is not the owner");
vm.expectRevert("Ownable: caller is not the owner");
sysConf.setResourceConfig(config);
sysConf.setResourceConfig(config);
}
}
/// @dev Tests that `setResourceConfig` reverts if the min base fee
/// is greater than the maximum allowed base fee.
function test_setResourceConfig_badMinMax_reverts() external {
function test_setResourceConfig_badMinMax_reverts() external {
ResourceMetering.ResourceConfig memory config = ResourceMetering.ResourceConfig({
ResourceMetering.ResourceConfig memory config = ResourceMetering.ResourceConfig({
maxResourceLimit: 20_000_000,
maxResourceLimit: 20_000_000,
...
@@ -100,6 +117,8 @@ contract SystemConfig_Setters_TestFail is SystemConfig_Init {
...
@@ -100,6 +117,8 @@ contract SystemConfig_Setters_TestFail is SystemConfig_Init {
sysConf.setResourceConfig(config);
sysConf.setResourceConfig(config);
}
}
/// @dev Tests that `setResourceConfig` reverts if the baseFeeMaxChangeDenominator
/// is zero.
function test_setResourceConfig_zeroDenominator_reverts() external {
function test_setResourceConfig_zeroDenominator_reverts() external {
ResourceMetering.ResourceConfig memory config = ResourceMetering.ResourceConfig({
ResourceMetering.ResourceConfig memory config = ResourceMetering.ResourceConfig({
maxResourceLimit: 20_000_000,
maxResourceLimit: 20_000_000,
...
@@ -114,6 +133,7 @@ contract SystemConfig_Setters_TestFail is SystemConfig_Init {
...
@@ -114,6 +133,7 @@ contract SystemConfig_Setters_TestFail is SystemConfig_Init {
sysConf.setResourceConfig(config);
sysConf.setResourceConfig(config);
}
}
/// @dev Tests that `setResourceConfig` reverts if the gas limit is too low.
function test_setResourceConfig_lowGasLimit_reverts() external {
function test_setResourceConfig_lowGasLimit_reverts() external {
uint64 gasLimit = sysConf.gasLimit();
uint64 gasLimit = sysConf.gasLimit();
...
@@ -130,6 +150,8 @@ contract SystemConfig_Setters_TestFail is SystemConfig_Init {
...
@@ -130,6 +150,8 @@ contract SystemConfig_Setters_TestFail is SystemConfig_Init {
sysConf.setResourceConfig(config);
sysConf.setResourceConfig(config);
}
}
/// @dev Tests that `setResourceConfig` reverts if the elasticity multiplier
/// and max resource limit are configured such that there is a loss of precision.
function test_setResourceConfig_badPrecision_reverts() external {
function test_setResourceConfig_badPrecision_reverts() external {
ResourceMetering.ResourceConfig memory config = ResourceMetering.ResourceConfig({
ResourceMetering.ResourceConfig memory config = ResourceMetering.ResourceConfig({
maxResourceLimit: 20_000_000,
maxResourceLimit: 20_000_000,
...
@@ -152,6 +174,7 @@ contract SystemConfig_Setters_Test is SystemConfig_Init {
...
@@ -152,6 +174,7 @@ contract SystemConfig_Setters_Test is SystemConfig_Init {
bytes data
bytes data
);
);
/// @dev Tests that `setBatcherHash` updates the batcher hash successfully.
function testFuzz_setBatcherHash_succeeds(bytes32 newBatcherHash) external {
function testFuzz_setBatcherHash_succeeds(bytes32 newBatcherHash) external {
vm.expectEmit(true, true, true, true);
vm.expectEmit(true, true, true, true);
emit ConfigUpdate(0, SystemConfig.UpdateType.BATCHER, abi.encode(newBatcherHash));
emit ConfigUpdate(0, SystemConfig.UpdateType.BATCHER, abi.encode(newBatcherHash));
...
@@ -161,6 +184,7 @@ contract SystemConfig_Setters_Test is SystemConfig_Init {
...
@@ -161,6 +184,7 @@ contract SystemConfig_Setters_Test is SystemConfig_Init {
assertEq(sysConf.batcherHash(), newBatcherHash);
assertEq(sysConf.batcherHash(), newBatcherHash);
}
}
/// @dev Tests that `setGasConfig` updates the overhead and scalar successfully.
function testFuzz_setGasConfig_succeeds(uint256 newOverhead, uint256 newScalar) external {
function testFuzz_setGasConfig_succeeds(uint256 newOverhead, uint256 newScalar) external {
vm.expectEmit(true, true, true, true);
vm.expectEmit(true, true, true, true);
emit ConfigUpdate(
emit ConfigUpdate(
...
@@ -175,6 +199,7 @@ contract SystemConfig_Setters_Test is SystemConfig_Init {
...
@@ -175,6 +199,7 @@ contract SystemConfig_Setters_Test is SystemConfig_Init {
assertEq(sysConf.scalar(), newScalar);
assertEq(sysConf.scalar(), newScalar);
}
}
/// @dev Tests that `setGasLimit` updates the gas limit successfully.
function testFuzz_setGasLimit_succeeds(uint64 newGasLimit) external {
function testFuzz_setGasLimit_succeeds(uint64 newGasLimit) external {
uint64 minimumGasLimit = sysConf.minimumGasLimit();
uint64 minimumGasLimit = sysConf.minimumGasLimit();
newGasLimit = uint64(
newGasLimit = uint64(
...
@@ -189,6 +214,7 @@ contract SystemConfig_Setters_Test is SystemConfig_Init {
...
@@ -189,6 +214,7 @@ contract SystemConfig_Setters_Test is SystemConfig_Init {
assertEq(sysConf.gasLimit(), newGasLimit);
assertEq(sysConf.gasLimit(), newGasLimit);
}
}
/// @dev Tests that `setUnsafeBlockSigner` updates the block signer successfully.
function testFuzz_setUnsafeBlockSigner_succeeds(address newUnsafeSigner) external {
function testFuzz_setUnsafeBlockSigner_succeeds(address newUnsafeSigner) external {
vm.expectEmit(true, true, true, true);
vm.expectEmit(true, true, true, true);
emit ConfigUpdate(
emit ConfigUpdate(
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment