From a90e302558bc3ad7404bf704654b0ac12586862a Mon Sep 17 00:00:00 2001 From: Vinod Damle Date: Mon, 16 Sep 2024 11:41:11 -0400 Subject: [PATCH] Config: Add contract impl addresses & versions to config toml (#525) * Config: Add standard contract impl addresses & versions to superchain config toml * store and expose the contract implementation addresses & versions for each contracts release. this is intended to be used by internal/external components such as validation code within superchain-registry, op-stack-manager, monorepo upgrade tool etc. The intent is to persist a single source of truth for the implementation addresses. * the addresses are stored in `superchain.toml` as a map of the contracts release tag as a string to the implementation contract addresses * Initial addresses/versions are recorded for v1.3.0/MCP L1 contracts release * add contract addresses & versions for sepolia (testnet) * Move contract implementation addresses under validation dir * contract implemetnation addresses now live with contract versions in validation * Instead of two separate files/sections, they are now specified as a `VersionedContract` which is a pre-existing construct in SCR. * Run just codegen * Address review comments moved `op_contracts_release` from superchain.toml to `validation/standard/standard-config.toml` * Fix naming and update addresses * Print a more useful diff for contract version check (#582) * update standard release * make a more useful diff * update go mod --------- Co-authored-by: Vinod Damle * Use optional fields to track either `implementation_address` or `address` in standard contract versions (#587) * VersionedContract has optional implementation_address and address fields * use pointers --------- Co-authored-by: Vinod Damle Co-authored-by: George Knee --- superchain/superchain.go | 89 +++++++++++++------ superchain/superchain_test.go | 12 +-- validation/go.mod | 1 + validation/standard/init.go | 4 +- validation/standard/standard-versions.toml | 56 +++++++++--- validation/standard/versions.go | 10 ++- validation/superchain-version_test.go | 99 ++++++++++++---------- 7 files changed, 173 insertions(+), 98 deletions(-) diff --git a/superchain/superchain.go b/superchain/superchain.go index 437bf6c1d..269fd986d 100644 --- a/superchain/superchain.go +++ b/superchain/superchain.go @@ -374,16 +374,10 @@ func (a AddressList) AddressFor(name string) (Address, error) { // contract. They are keyed by the semantic version. type AddressSet map[string]Address -// VersionedContract represents a contract that has a semantic version. -type VersionedContract struct { - Version string `json:"version" toml:"version"` - Address Address `json:"address" toml:"address"` -} - // ContractVersions represents the desired semantic version of the contracts // in the superchain. This currently only supports L1 contracts but could // represent L2 predeploys in the future. -type ContractVersions struct { +type ContractBytecodeHashes struct { L1CrossDomainMessenger string `toml:"l1_cross_domain_messenger"` L1ERC721Bridge string `toml:"l1_erc721_bridge"` L1StandardBridge string `toml:"l1_standard_bridge"` @@ -404,43 +398,82 @@ type ContractVersions struct { PreimageOracle string `toml:"preimage_oracle,omitempty"` } +// VersionedContract represents a contract that has a semantic version. +type VersionedContract struct { + Version string `toml:"version"` + // If the contract is a superchain singleton, it will have a static address + Address *Address `toml:"implementation_address,omitempty"` + // If the contract is proxied, the implementation will have a static address + ImplementationAddress *Address `toml:"address,omitempty"` +} + +// ContractVersions represents the desired semantic version of the contracts +// in the superchain. This currently only supports L1 contracts but could +// represent L2 predeploys in the future. +type ContractVersions struct { + L1CrossDomainMessenger VersionedContract `toml:"l1_cross_domain_messenger,omitempty"` + L1ERC721Bridge VersionedContract `toml:"l1_erc721_bridge,omitempty"` + L1StandardBridge VersionedContract `toml:"l1_standard_bridge,omitempty"` + L2OutputOracle VersionedContract `toml:"l2_output_oracle,omitempty"` + OptimismMintableERC20Factory VersionedContract `toml:"optimism_mintable_erc20_factory,omitempty"` + OptimismPortal VersionedContract `toml:"optimism_portal,omitempty"` + OptimismPortal2 VersionedContract `toml:"optimism_portal2,omitempty"` + SystemConfig VersionedContract `toml:"system_config,omitempty"` + // Superchain-wide contracts: + ProtocolVersions VersionedContract `toml:"protocol_versions,omitempty"` + SuperchainConfig VersionedContract `toml:"superchain_config,omitempty"` + // Fault Proof contracts: + AnchorStateRegistry VersionedContract `toml:"anchor_state_registry,omitempty"` + DelayedWETH VersionedContract `toml:"delayed_weth,omitempty"` + DisputeGameFactory VersionedContract `toml:"dispute_game_factory,omitempty"` + FaultDisputeGame VersionedContract `toml:"fault_dispute_game,omitempty"` + MIPS VersionedContract `toml:"mips,omitempty"` + PermissionedDisputeGame VersionedContract `toml:"permissioned_dispute_game,omitempty"` + PreimageOracle VersionedContract `toml:"preimage_oracle,omitempty"` + CannonFaultDisputeGame VersionedContract `toml:"cannon_fault_dispute_game,omitempty"` +} + // VersionFor returns the version for the supplied contract name, if it exits // (and an error otherwise). Useful for slicing into the struct using a string. func (c ContractVersions) VersionFor(contractName string) (string, error) { var version string switch contractName { case "L1CrossDomainMessenger": - version = c.L1CrossDomainMessenger + version = c.L1CrossDomainMessenger.Version case "L1ERC721Bridge": - version = c.L1ERC721Bridge + version = c.L1ERC721Bridge.Version case "L1StandardBridge": - version = c.L1StandardBridge + version = c.L1StandardBridge.Version case "L2OutputOracle": - version = c.L2OutputOracle + version = c.L2OutputOracle.Version case "OptimismMintableERC20Factory": - version = c.OptimismMintableERC20Factory + version = c.OptimismMintableERC20Factory.Version case "OptimismPortal": - version = c.OptimismPortal + version = c.OptimismPortal.Version + case "OptimismPortal2": + version = c.OptimismPortal2.Version case "SystemConfig": - version = c.SystemConfig + version = c.SystemConfig.Version case "AnchorStateRegistry": - version = c.AnchorStateRegistry + version = c.AnchorStateRegistry.Version case "DelayedWETH": - version = c.DelayedWETH + version = c.DelayedWETH.Version case "DisputeGameFactory": - version = c.DisputeGameFactory + version = c.DisputeGameFactory.Version case "FaultDisputeGame": - version = c.FaultDisputeGame + version = c.FaultDisputeGame.Version case "MIPS": - version = c.MIPS + version = c.MIPS.Version case "PermissionedDisputeGame": - version = c.PermissionedDisputeGame + version = c.PermissionedDisputeGame.Version case "PreimageOracle": - version = c.PreimageOracle + version = c.PreimageOracle.Version case "ProtocolVersions": - version = c.ProtocolVersions + version = c.ProtocolVersions.Version case "SuperchainConfig": - version = c.SuperchainConfig + version = c.SuperchainConfig.Version + case "CannonFaultDisputeGame": + version = c.CannonFaultDisputeGame.Version default: return "", errors.New("no such contract name") } @@ -456,19 +489,19 @@ func (c ContractVersions) Check(allowEmptyVersions bool) error { val := reflect.ValueOf(c) for i := 0; i < val.NumField(); i++ { field := val.Field(i) - str, ok := field.Interface().(string) + vC, ok := field.Interface().(VersionedContract) if !ok { return fmt.Errorf("invalid type for field %s", val.Type().Field(i).Name) } - if str == "" { + if vC.Version == "" { if allowEmptyVersions { continue // we allow empty strings and rely on tests to assert (or except) a nonempty version } return fmt.Errorf("empty version for field %s", val.Type().Field(i).Name) } - str = CanonicalizeSemver(str) - if !semver.IsValid(str) { - return fmt.Errorf("invalid semver %s for field %s", str, val.Type().Field(i).Name) + vC.Version = CanonicalizeSemver(vC.Version) + if !semver.IsValid(vC.Version) { + return fmt.Errorf("invalid semver %s for field %s", vC.Version, val.Type().Field(i).Name) } } return nil diff --git a/superchain/superchain_test.go b/superchain/superchain_test.go index 4cada1d82..f9f6b7d62 100644 --- a/superchain/superchain_test.go +++ b/superchain/superchain_test.go @@ -26,8 +26,8 @@ func TestAddressFor(t *testing.T) { func TestVersionFor(t *testing.T) { cl := ContractVersions{ - L1CrossDomainMessenger: "1.9.9", - OptimismPortal: "", + L1CrossDomainMessenger: VersionedContract{Version: "1.9.9"}, + OptimismPortal: VersionedContract{Version: ""}, } want := "1.9.9" got, err := cl.VersionFor("L1CrossDomainMessenger") @@ -208,12 +208,14 @@ ecotone_time = 3 err := unMarshalSuperchainConfig([]byte(rawTOML), &s) require.NoError(t, err) - require.Equal(t, "Mickey Mouse", s.Name) - require.Equal(t, SuperchainL1Info{ + expectL1Info := SuperchainL1Info{ ChainID: 314, PublicRPC: "https://disney.com", Explorer: "https://disneyscan.io", - }, s.L1) + } + + require.Equal(t, "Mickey Mouse", s.Name) + require.Equal(t, expectL1Info, s.L1) require.Equal(t, "0x252CbE9517F731C618961D890D534183822dcC8d", s.ProtocolVersionsAddr.String()) require.Equal(t, "0x02d91Cf852423640d93920BE0CAdceC0E7A00FA7", s.SuperchainConfigAddr.String()) diff --git a/validation/go.mod b/validation/go.mod index a752fc732..29f9f4f83 100644 --- a/validation/go.mod +++ b/validation/go.mod @@ -11,6 +11,7 @@ require ( github.com/ethereum-optimism/optimism v1.9.1-0.20240814195148-0bb2ff57c813 github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20240828144951-4e6edcb7d36c github.com/ethereum/go-ethereum v1.14.7 + github.com/google/go-cmp v0.6.0 github.com/stretchr/testify v1.9.0 golang.org/x/mod v0.20.0 ) diff --git a/validation/standard/init.go b/validation/standard/init.go index e84cc4341..b37a997eb 100644 --- a/validation/standard/init.go +++ b/validation/standard/init.go @@ -21,7 +21,7 @@ var standardConfigFile embed.FS var ContractASTsWithImmutableReferences = map[string]string{} // L1ContractBytecodeHashes represents the hash of the contract bytecode (as a hex string) for each L1 contract -type L1ContractBytecodeHashes superchain.ContractVersions +type L1ContractBytecodeHashes superchain.ContractBytecodeHashes // ContractBytecodeImmutables stores the immutable references as a raw stringified JSON string in a TOML config. // it is stored this way because it can be plucked out of the contract compilation output as is and pasted into the TOML config file. @@ -71,7 +71,7 @@ func decodeTOMLFileIntoConfig[T Params | Roles | MultisigRoles | VersionTags | B // LoadImmutableReferences parses standard-immutables.toml and stores it in a map. Needs to be invoked one-time only. func LoadImmutableReferences() { var bytecodeImmutables *ContractBytecodeImmutables - for tag := range Versions { + for tag := range Versions.Releases { for contractVersion, immutables := range BytecodeImmutables { if tag == contractVersion { bytecodeImmutables = &immutables diff --git a/validation/standard/standard-versions.toml b/validation/standard/standard-versions.toml index 17d178890..2efeab335 100644 --- a/validation/standard/standard-versions.toml +++ b/validation/standard/standard-versions.toml @@ -1,14 +1,42 @@ -["op-contracts/v1.4.0"] # Fault Proofs https://github.com/ethereum-optimism/optimism/releases/tag/op-contracts%2Fv1.4.0 -address_manager = "" -l1_cross_domain_messenger = "2.3.0" -l1_erc721_bridge = "2.1.0" -l1_standard_bridge = "2.1.0" -optimism_mintable_erc20_factory = "1.9.0" -optimism_portal = "3.10.0" -system_config = "2.2.0" -protocol_versions = "1.0.0" -anchor_state_registry = "1.0.0" -delayed_weth = "1.0.0" -dispute_game_factory = "1.0.0" -fault_dispute_game = "1.2.0" -permissioned_dispute_game = "1.2.0" +standard_release = "op-contracts/v1.6.0" + +[releases] + +# Contracts which are +# * unproxied singletons: specify a standard "address" +# * proxied : specify a standard "implementation_address" +# * neither : specify neither a standard "address" nor "implementation_address" + +# Fault Proofs https://github.com/ethereum-optimism/optimism/releases/tag/op-contracts%2Fv1.6.0 +[releases."op-contracts/v1.6.0"] +optimism_portal = { version = "3.10.0", implementation_address = "0xe2F826324b2faf99E513D16D266c3F80aE87832B" } +system_config = { version = "2.2.0", implementation_address = "0xF56D96B2535B932656d3c04Ebf51baBff241D886" } +anchor_state_registry = { version = "2.0.0" } +delayed_weth = { version = "1.1.0", implementation_address = "0x71e966Ae981d1ce531a7b6d23DC0f27B38409087" } +dispute_game_factory = { version = "1.0.0", implementation_address = "0xc641A33cab81C559F2bd4b21EA34C290E2440C2B" } +fault_dispute_game = { version = "1.3.0" } +permissioned_dispute_game = { version = "1.3.0" } +mips = { version = "1.1.0", address = "0x16e83cE5Ce29BF90AD9Da06D2fE6a15d5f344ce4" } +preimage_oracle = { version = "1.1.2", address = "0x9c065e11870B891D214Bc2Da7EF1f9DDFA1BE277" } + +# Fault Proofs https://github.com/ethereum-optimism/optimism/releases/tag/op-contracts%2Fv1.4.0 +[releases."op-contracts/v1.4.0"] +optimism_portal = { version = "3.10.0", implementation_address = "0xe2F826324b2faf99E513D16D266c3F80aE87832B" } +system_config = { version = "2.2.0", implementation_address = "0xF56D96B2535B932656d3c04Ebf51baBff241D886" } +anchor_state_registry = { version = "1.0.0" } +delayed_weth = { version = "1.0.0", implementation_address = "0x97988d5624F1ba266E1da305117BCf20713bee08" } +dispute_game_factory = { version = "1.0.0", implementation_address = "0xc641A33cab81C559F2bd4b21EA34C290E2440C2B" } +fault_dispute_game = { version = "1.2.0" } +permissioned_dispute_game = { version = "1.2.0" } +mips = { version = "1.0.1", address = "0x0f8EdFbDdD3c0256A80AD8C0F2560B1807873C9c" } +preimage_oracle = { version = "1.0.0", address = "0xD326E10B8186e90F4E2adc5c13a2d0C137ee8b34" } + +# MCP https://github.com/ethereum-optimism/optimism/releases/tag/op-contracts%2Fv1.3.0 +[releases."op-contracts/v1.3.0"] +l1_cross_domain_messenger = { version = "2.3.0", implementation_address = "0xD3494713A5cfaD3F5359379DfA074E2Ac8C6Fd65" } +l1_erc721_bridge = { version = "2.1.0", implementation_address = "0xAE2AF01232a6c4a4d3012C5eC5b1b35059caF10d" } +l1_standard_bridge = { version = "2.1.0", implementation_address = "0x64B5a5Ed26DCb17370Ff4d33a8D503f0fbD06CfF" } +l2_output_oracle = { version = "1.8.0", implementation_address = "0xF243BEd163251380e78068d317ae10f26042B292" } +optimism_mintable_erc20_factory = { version = "1.9.0", implementation_address = "0xE01efbeb1089D1d1dB9c6c8b135C934C0734c846" } +optimism_portal = { version = "2.5.0", implementation_address = "0x2D778797049FE9259d947D1ED8e5442226dFB589" } +system_config = { version = "1.12.0", implementation_address = "0xba2492e52F45651B60B8B38d4Ea5E2390C64Ffb1" } diff --git a/validation/standard/versions.go b/validation/standard/versions.go index d8e35e838..9204135cc 100644 --- a/validation/standard/versions.go +++ b/validation/standard/versions.go @@ -7,13 +7,19 @@ import ( type Tag string type ( - VersionTags = map[Tag]superchain.ContractVersions BytecodeHashTags = map[Tag]L1ContractBytecodeHashes BytecodeImmutablesTags = map[Tag]ContractBytecodeImmutables ) +type VersionTags struct { + Releases map[Tag]superchain.ContractVersions `toml:"releases"` + StandardRelease Tag `toml:"standard_release,omitempty"` +} + var ( - Versions VersionTags = make(VersionTags, 0) + Versions VersionTags = VersionTags{ + Releases: make(map[Tag]superchain.ContractVersions, 0), + } BytecodeHashes BytecodeHashTags = make(BytecodeHashTags, 0) BytecodeImmutables BytecodeImmutablesTags = make(BytecodeImmutablesTags, 0) ) diff --git a/validation/superchain-version_test.go b/validation/superchain-version_test.go index 78700b07b..2de766faf 100644 --- a/validation/superchain-version_test.go +++ b/validation/superchain-version_test.go @@ -12,6 +12,7 @@ import ( . "github.com/ethereum-optimism/superchain-registry/superchain" "github.com/ethereum-optimism/superchain-registry/validation/internal/bindings" "github.com/ethereum-optimism/superchain-registry/validation/standard" + "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/require" "github.com/ethereum/go-ethereum" @@ -55,8 +56,18 @@ func testContractsMatchATag(t *testing.T, chain *ChainConfig) { versions, err := getContractVersionsFromChain(*Addresses[chain.ChainID], client) require.NoError(t, err) - _, err = findOPContractTagInVersions(versions, isTestnet) + tag, _ := findOPContractTagInVersions(versions, isTestnet) + + standardRelease := standard.Versions.StandardRelease + standardVersions := standard.Versions.Releases[standardRelease] + + v, err := json.MarshalIndent(versions, "", " ") require.NoError(t, err) + sv, err := json.MarshalIndent(standardVersions, "", " ") + require.NoError(t, err) + diff := cmp.Diff(v, sv) + + require.Equalf(t, standardRelease, tag, "did not match standard release %s: (-removed from standard / +added to actual) %s", standardRelease, diff) // don't perform bytecode checking for testnets if !isTestnet { @@ -111,10 +122,11 @@ func getContractVersionsFromChain(list AddressList, client *ethclient.Client) (C // which includes both proxied and unproxied contracts. // The cv object (of type ContractVersions), on the other hand, // only lists implementation contract versions. The next line accounts for - // this: we may get the version directly from the implemntation, or via a Proxy, + // this: we may get the version directly from the implementation, or via a Proxy, // but we store it against the implementation name in either case. if s.Type().Field(i).Name == k || s.Type().Field(i).Name+"Proxy" == k { - reflect.ValueOf(&cv).Elem().Field(i).SetString(v.(string)) + // there is an inner struct, a VersionedContract - set the `Version` + reflect.ValueOf(&cv).Elem().Field(i).FieldByName("Version").SetString(v.(string)) } } return true @@ -269,22 +281,22 @@ func getBytecodeHash(ctx context.Context, chainID uint64, contractName string, t func TestFindOPContractTag(t *testing.T) { shouldMatch := ContractVersions{ - L1CrossDomainMessenger: "2.3.0", - L1ERC721Bridge: "2.1.0", - L1StandardBridge: "2.1.0", - L2OutputOracle: "", - OptimismMintableERC20Factory: "1.9.0", - OptimismPortal: "3.10.0", - SystemConfig: "2.2.0", - ProtocolVersions: "", - SuperchainConfig: "", - AnchorStateRegistry: "1.0.0", - DelayedWETH: "1.0.0", - DisputeGameFactory: "1.0.0", - FaultDisputeGame: "1.2.0", - MIPS: "1.0.1", - PermissionedDisputeGame: "1.2.0", - PreimageOracle: "1.0.0", + L1CrossDomainMessenger: VersionedContract{Version: "2.3.0"}, + L1ERC721Bridge: VersionedContract{Version: "2.1.0"}, + L1StandardBridge: VersionedContract{Version: "2.1.0"}, + L2OutputOracle: VersionedContract{Version: ""}, + OptimismMintableERC20Factory: VersionedContract{Version: "1.9.0"}, + OptimismPortal: VersionedContract{Version: "3.10.0"}, + SystemConfig: VersionedContract{Version: "2.2.0"}, + ProtocolVersions: VersionedContract{Version: "1.0.0"}, + SuperchainConfig: VersionedContract{Version: ""}, + AnchorStateRegistry: VersionedContract{Version: "1.0.0"}, + DelayedWETH: VersionedContract{Version: "1.0.0"}, + DisputeGameFactory: VersionedContract{Version: "1.0.0"}, + FaultDisputeGame: VersionedContract{Version: "1.2.0"}, + MIPS: VersionedContract{Version: "1.0.1"}, + PermissionedDisputeGame: VersionedContract{Version: "1.2.0"}, + PreimageOracle: VersionedContract{Version: "1.0.0"}, } got, err := findOPContractTagInVersions(shouldMatch, false) @@ -293,14 +305,14 @@ func TestFindOPContractTag(t *testing.T) { require.Equal(t, got, want) shouldNotMatch := ContractVersions{ - L1CrossDomainMessenger: "2.3.0", - L1ERC721Bridge: "2.1.0", - L1StandardBridge: "2.1.0", - OptimismMintableERC20Factory: "1.9.0", - OptimismPortal: "2.5.0", - SystemConfig: "1.12.0", - ProtocolVersions: "1.0.0", - L2OutputOracle: "1.0.0", + L1CrossDomainMessenger: VersionedContract{Version: "2.3.0"}, + L1ERC721Bridge: VersionedContract{Version: "2.1.0"}, + L1StandardBridge: VersionedContract{Version: "2.1.0"}, + OptimismMintableERC20Factory: VersionedContract{Version: "1.9.0"}, + OptimismPortal: VersionedContract{Version: "2.5.0"}, + SystemConfig: VersionedContract{Version: "1.12.0"}, + ProtocolVersions: VersionedContract{Version: "1.0.0"}, + L2OutputOracle: VersionedContract{Version: "1.0.0"}, } got, err = findOPContractTagInVersions(shouldNotMatch, false) require.Error(t, err) @@ -310,17 +322,7 @@ func TestFindOPContractTag(t *testing.T) { func findOPContractTagInVersions(versions ContractVersions, isTestnet bool) ([]standard.Tag, error) { matchingTags := make([]standard.Tag, 0) - pretty, err := json.MarshalIndent(versions, "", " ") - if err != nil { - return matchingTags, err - } - - prettyStandard, err := json.MarshalIndent(standard.Versions, "", " ") - if err != nil { - return matchingTags, err - } - - err = fmt.Errorf("contract versions %s do not match any standard op-contracts tag %s", pretty, prettyStandard) + err := fmt.Errorf("contract versions do not match any standard op-contracts tag") matchesTag := func(standard, candidate ContractVersions) bool { s := reflect.ValueOf(standard) @@ -328,8 +330,8 @@ func findOPContractTagInVersions(versions ContractVersions, isTestnet bool) ([]s return checkMatchOrTestnet(s, c, isTestnet) } - for tag := range standard.Versions { - if matchesTag(standard.Versions[tag], versions) { + for tag := range standard.Versions.Releases { + if matchesTag(standard.Versions.Releases[tag], versions) { matchingTags = append(matchingTags, tag) err = nil } @@ -357,7 +359,7 @@ func findOPContractTagInByteCodeHashes(hashes standard.L1ContractBytecodeHashes) return checkMatch(s, c) } - for tag := range standard.Versions { + for tag := range standard.Versions.Releases { if matchesTag(standard.BytecodeHashes[tag], hashes) { matchingTags = append(matchingTags, tag) err = nil @@ -405,26 +407,29 @@ func checkMatchOrTestnet(s, c reflect.Value, isTestnet bool) bool { continue } - field := s.Field(i) + innerStStd := s.Field(i).Interface().(VersionedContract) + innerFieldS := reflect.ValueOf(innerStStd).FieldByName("Version") + innerStCand := c.Field(i).Interface().(VersionedContract) + innerFieldC := reflect.ValueOf(innerStCand).FieldByName("Version") - if field.Kind() != reflect.String { + if innerFieldS.Kind() != reflect.String { panic("versions must be strings") } - if field.String() == "" { + if innerFieldS.String() == "" { // Ignore any empty strings, these are treated as "match anything" continue } - if field.String() != c.Field(i).String() { + if innerFieldS.String() != innerFieldC.String() { if !isTestnet { return false } // testnets are permitted to have contract versions that are newer than what's specified in the standard config // testnets may NOT have contract versions that are older. - min := CanonicalizeSemver(field.String()) - current := CanonicalizeSemver(c.Field(i).String()) + min := CanonicalizeSemver(innerFieldS.String()) + current := CanonicalizeSemver(innerFieldC.String()) if semver.Compare(min, current) > 0 { return false }