Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: invariant tests for OptimismSuperchainERC20 #11776

Merged
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
# `OptimismSuperchainERC20` Invariants

## Calls to sendERC20 should always succeed as long as the actor has enough balance. Actor's balance should also not increase out of nowhere but instead should decrease by the amount sent.
**Test:** [`OptimismSuperchainERC20.t.sol#L196`](../test/invariants/OptimismSuperchainERC20.t.sol#L196)
## sum of supertoken total supply across all chains is always <= to convert(legacy, super)- convert(super, legacy)
**Test:** [`OptimismSuperchainERC20#L36`](../test/invariants/OptimismSuperchainERC20#L36)



## Calls to relayERC20 should always succeeds when a message is received from another chain. Actor's balance should only increase by the amount relayed.
**Test:** [`OptimismSuperchainERC20.t.sol#L214`](../test/invariants/OptimismSuperchainERC20.t.sol#L214)
## sum of supertoken total supply across all chains is equal to convert(legacy, super)- convert(super, legacy) when all when all cross-chain messages are processed
**Test:** [`OptimismSuperchainERC20#L57`](../test/invariants/OptimismSuperchainERC20#L57)



## many other assertion mode invariants are also defined under `test/invariants/OptimismSuperchainERC20/fuzz/` .
**Test:** [`OptimismSuperchainERC20#L80`](../test/invariants/OptimismSuperchainERC20#L80)

since setting`fail_on_revert=false` also ignores StdAssertion failures, this invariant explicitly asks the handler for assertion test failures
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ func docGen(invariantsDir, docsDir string) error {
// Read the contents of the invariant test file.
fileName := file.Name()
filePath := filepath.Join(invariantsDir, fileName)
// where invariants for a module have their own directory, interpret
// the test file with the same name as the directory as a 'main' of
// sorts, from where documentation is pulled
if file.IsDir() {
filePath = filepath.Join(filePath, strings.Join([]string{fileName, ".t.sol"}, ""))
}
fileContents, err := os.ReadFile(filePath)
if err != nil {
return fmt.Errorf("error reading file %q: %w", filePath, err)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ for FILE in $CHANGED_FILES; do
echo "skipping $FILE"
continue
fi
if [ ! -e "$FILE" ] ; then
echo "skipping $FILE since it was deleted"
continue
fi

# Get the diff for the file.
DIFF=$(git diff origin/develop...HEAD --unified=0 -- "$FILE")
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

// Testing utilities
import { Test, StdUtils, Vm } from "forge-std/Test.sol";
import { StdInvariant } from "forge-std/StdInvariant.sol";
import { StdAssertions } from "forge-std/StdAssertions.sol";
import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol";

// Libraries
import { Predeploys } from "src/libraries/Predeploys.sol";
import { OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20.sol";
import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol";
import { ProtocolGuided } from "./fuzz/Protocol.guided.t.sol";
import { ProtocolUnguided } from "./fuzz/Protocol.unguided.t.sol";
import { HandlerGetters } from "./helpers/HandlerGetters.t.sol";
import { MockL2ToL2CrossDomainMessenger } from "./helpers/MockL2ToL2CrossDomainMessenger.t.sol";

contract OptimismSuperchainERC20Handler is HandlerGetters, ProtocolGuided, ProtocolUnguided { }

contract OptimismSuperchainERC20Properties is Test {
OptimismSuperchainERC20Handler internal handler;
MockL2ToL2CrossDomainMessenger internal constant MESSENGER =
MockL2ToL2CrossDomainMessenger(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER);

function setUp() public {
handler = new OptimismSuperchainERC20Handler();
targetContract(address(handler));
}

// TODO: will need rework after
// - `convert`
/// @custom:invariant sum of supertoken total supply across all chains is always <= to convert(legacy, super)-
/// convert(super, legacy)
function invariant_totalSupplyAcrossChainsEqualsMintsMinusFundsInTransit() external view {
// iterate over unique deploy salts aka supertokens that are supposed to be compatible with each other
for (uint256 deploySaltIndex = 0; deploySaltIndex < handler.deploySaltsLength(); deploySaltIndex++) {
uint256 totalSupply = 0;
(bytes32 currentSalt, uint256 trackedSupply) = handler.totalSupplyAcrossChainsAtIndex(deploySaltIndex);
uint256 fundsInTransit = handler.tokensInTransitForDeploySalt(currentSalt);
// and then over all the (mocked) chain ids where that supertoken could be deployed
for (uint256 validChainId = 0; validChainId < handler.MAX_CHAINS(); validChainId++) {
address supertoken = MESSENGER.superTokenAddresses(validChainId, currentSalt);
if (supertoken != address(0)) {
totalSupply += OptimismSuperchainERC20(supertoken).totalSupply();
}
}
assertEq(trackedSupply, totalSupply + fundsInTransit);
}
}

// TODO: will need rework after
// - `convert`
/// @custom:invariant sum of supertoken total supply across all chains is equal to convert(legacy, super)-
/// convert(super, legacy) when all when all cross-chain messages are processed
function invariant_totalSupplyAcrossChainsEqualsMintsWhenQueueIsEmpty() external view {
if (MESSENGER.messageQueueLength() != 0) {
return;
}
// iterate over unique deploy salts aka supertokens that are supposed to be compatible with each other
for (uint256 deploySaltIndex = 0; deploySaltIndex < handler.deploySaltsLength(); deploySaltIndex++) {
uint256 totalSupply = 0;
(bytes32 currentSalt, uint256 trackedSupply) = handler.totalSupplyAcrossChainsAtIndex(deploySaltIndex);
// and then over all the (mocked) chain ids where that supertoken could be deployed
for (uint256 validChainId = 0; validChainId < handler.MAX_CHAINS(); validChainId++) {
address supertoken = MESSENGER.superTokenAddresses(validChainId, currentSalt);
if (supertoken != address(0)) {
totalSupply += OptimismSuperchainERC20(supertoken).totalSupply();
}
}
assertEq(trackedSupply, totalSupply);
}
}

/// @custom:invariant many other assertion mode invariants are also defined under
/// `test/invariants/OptimismSuperchainERC20/fuzz/` .
///
/// since setting`fail_on_revert=false` also ignores StdAssertion failures, this invariant explicitly asks the
/// handler for assertion test failures
function invariant_handlerAssertions() external view {
assertFalse(handler.failed());
}
}
Loading