Skip to content

refactor: convert abstract contracts to libraries - #353

Draft
zguesmi wants to merge 14 commits into
refactor/remove-duplicate-eventsfrom
refactor/convert-abstract-to-libraries
Draft

refactor: convert abstract contracts to libraries#353
zguesmi wants to merge 14 commits into
refactor/remove-duplicate-eventsfrom
refactor/convert-abstract-to-libraries

Conversation

@zguesmi

@zguesmi zguesmi commented Sep 1, 2026

Copy link
Copy Markdown
Member
  • contracts/abstract/IexecPocoCommon.solcontracts/libs/CommonLib.sol
  • contracts/abstract/SignatureVerifier.solcontracts/libs/CommonLib.sol
  • contracts/abstract/IexecEscrow.solcontracts/libs/EscrowLib.sol

New digram

flowchart LR
    subgraph abstract
        FacetBase
    end
    subgraph libs
        CommonLib
        EscrowLib
    end
    IexecAccessorsABILegacyFacet --> FacetBase
    IexecCategoryManagerFacet --> FacetBase
    IexecConfigurationExtraFacet --> FacetBase
    IexecConfigurationFacet --> FacetBase
    IexecEscrowTokenFacet --> FacetBase
    IexecOrderManagementFacet --> FacetBase
    IexecPoco1Facet --> FacetBase
    IexecPoco2Facet --> FacetBase
    IexecPocoAccessorsFacet --> FacetBase
    IexecPocoBoostAccessorsFacet --> FacetBase
    IexecPocoBoostFacet --> FacetBase
    IexecRelayFacet --> FacetBase
    IexecEscrowTokenFacet -.-> EscrowLib
    IexecOrderManagementFacet -.-> CommonLib
Loading

The abstract contract was a stateless helper over diamond storage that
used nothing from FacetBase, so inheriting it forced three facets into a
base they did not need. A library expresses the same thing without the
inheritance edge, and `internal` library functions are inlined, so the
call sites keep their previous cost.

The function loses its leading underscore, which marked it as internal to
a contract and means nothing in a library. The two `using IexecLibOrders_v5`
directives were unreferenced and are dropped.

No ABI change: the contract exported no ABI entries, having only internal
members.
…ibrary

Like the deal volume helper, the abstract contract was a stateless helper
over diamond storage and imposed itself as a base on five facets. A
library removes that inheritance edge; `internal` library functions are
inlined, so the 66 call sites keep their previous cost.

The one member it did take from FacetBase, the GROUPMEMBER_PURPOSE
constant, is redeclared in the library, since a library cannot inherit.
The functions lose their leading underscores, which marked them as
internal to a contract and mean nothing in a library.

IexecPocoAccessorsFacet and IexecOrderManagementFacet reached FacetBase
only through SignatureVerifier and use it (the deadline ratios, the kitty
constants, _msgSender), so they now declare it directly. The other three
facets still reach it through IexecEscrow.

No ABI change: the contract exported no ABI entries, having only internal
members.
…ew libraries

The two converted helpers moved out of `contracts/abstract/`, so the
docgen exclude list named files that no longer exist. Both libraries are
still excluded for the same reason as before: they expose only internal
members, so they would document nothing.
The shorter name matches the other helper libraries and lets 29 of the 66
call sites fit back on a single line.
Converting SignatureVerifier to a library forced a second declaration of
GROUPMEMBER_PURPOSE, because a library cannot inherit FacetBase and a
contract constant is not reachable from outside the contract at any
visibility: solc rejects FacetBase.GROUPMEMBER_PURPOSE from a library with
"Member not found or not visible after argument-dependent lookup". A
library constant has no such restriction, so ConstantsLib becomes the
single declaration of all eight protocol constants.

FacetBase keeps the eight names as aliases of the library's values, so the
31 bare uses in the facets and the eight public getters on
IexecPocoAccessorsFacet are untouched.

No ABI change. Constant folding is preserved: the deployed bytecode still
holds one PUSH each for 1e9, the kitty address and the stake ratio, and
the same count of the literal 4. Four facets grew between 8 and 48 bytes
of jump-offset churn under viaIR; the largest, IexecPocoBoostFacet, sits
at 16665 of the 24576-byte limit.
This reverts commit 737221a. The constants move is deferred to the next
PR, so SignatureLib keeps its own GROUPMEMBER_PURPOSE for now and
FacetBase carries a TODO recording why the duplicate exists and what
replaces it.
The two libraries served the same five facets and each held helpers that
are stateless reads over diamond storage, so one library keeps the import
lists shorter without changing what the facets can reach. computeDealVolume
goes last, after the signature and restriction helpers.

The deployed bytecode of every facet is byte-identical to before the merge,
and no ABI changes: internal library functions are inlined either way.
The library and facet names still carry suffixes the next PR removes:
IexecLibCore_v5 becomes CoreLib, IexecLibOrders_v5 becomes OrdersLib and
IexecEscrowTokenFacet becomes IexecEscrowFacet. IexecInterfaceToken
already carried its own note.
The last of the three stateless helpers over diamond storage leaves
contracts/abstract/, which now holds FacetBase alone. The four facets that
inherited the escrow declare FacetBase directly instead; each of them uses
it. IexecEscrowTestContract becomes a plain wrapper around the library.

The library declares the five events it emits. solc 0.8.21 cannot compile a
qualified `emit IexecEscrowEvents.Lock(...)` when userdoc is requested and
docgen always requests it, so a second declaration is the only option;
IexecEscrowEvents stays as the declaration that feeds IexecInterfaceToken,
whose ABI is unchanged.

Events now follow the emit path, so the dead entries disappear from the
facet ABIs: IexecPoco1Facet drops from six events to Transfer and Lock,
and IexecPoco2Facet and IexecPocoBoostFacet drop Approval.

IexecEscrowTokenFacet is the exception. It reaches the events through
IexecEscrowToken and also emits Transfer through the library, so its ABI
lists Transfer twice and typechain generates 14 duplicate-identifier
errors for it. Moving _mint, _burn and _approve into the library removes
the duplicate; that is recorded as a TODO on the contract and deferred to
the next PR.

The members lose their leading underscores, which marked them as internal
to a contract, so _transfer is now EscrowLib.transfer. The revert strings
still read "IexecEscrow: ..." because tests assert on them.
The IexecEscrow section becomes EscrowLib and now documents the five events
the library declares. The class diagrams pick up CommonLib, EscrowLib and
the facets that reach FacetBase directly.

sol-to-uml.mjs named IexecEscrow as a base contract of the IexecEscrows
diagram; it is EscrowLib now.
Coverage failed with 25 errors as soon as the shared helpers became
libraries. The first failure is the real one, the other 24 cascade from it:

    TypeError: Cannot read properties of undefined (reading 'replace')
      at Function.linkBytecode (typechain/factories/.../IexecPocoAccessorsFacet__factory.ts)

CommonLib and EscrowLib expose only internal functions, so their code is
inlined and their addresses are never used at runtime. But solc's IR
pipeline emits a linkersymbol reference for every library a contract uses
and leaves the Yul unused pruner to drop it once it is unreachable. The
main build runs that pruner, `optimizerSteps: 'u'`; .solcover.js disabled
every step with `optimizerSteps: ''`, so the dead reference survived into
the artifact. Typechain then generated the linkLibraryAddresses variant of
the factory constructor for facets that need no linking, and
deploy/0_deploy.ts builds those with a signer alone.

The throw left CreateX half-deployed, so every later loadFixture re-ran
against dirty state and failed with "Nonce too low. Expected nonce to be 1
but got 0".

Enabling the pruner removes the reference. Verified: the only link
reference left on the facets is IexecLibOrders_v5, which does have public
functions and is linked in both builds; coverage compiles without the
stack-too-deep that a full optimizer step list causes; and the totals are
unchanged at 99.69 / 96.55 / 99.44 / 99.53, with CommonLib and EscrowLib
at 100.
@codecov

codecov Bot commented Sep 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.53%. Comparing base (bb5de40) to head (63f8f8f).

Additional details and impacted files
@@                        Coverage Diff                        @@
##           refactor/remove-duplicate-events     #353   +/-   ##
=================================================================
  Coverage                             99.53%   99.53%           
=================================================================
  Files                                    31       30    -1     
  Lines                                  1069     1069           
  Branches                                215      204   -11     
=================================================================
  Hits                                   1064     1064           
  Misses                                    5        5           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant