diff --git a/README.md b/README.md index 1c70e0f4..296dd73c 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,8 @@ Options: -hi, --hideInterfaces hide interfaces (default: false) -ha, --hideAbstracts hide abstract contracts (default: false) -hn, --hideFilename hide relative path and file name (default: false) + -s, --squash squash inherited contracts to the base contract(s) (default: false) + -hsc, --hideSourceContract hide the source contract when using squash (default: false) -h, --help display help for command ``` diff --git a/examples/CErc20-hide.svg b/examples/CErc20-hide.svg new file mode 100644 index 00000000..364252aa --- /dev/null +++ b/examples/CErc20-hide.svg @@ -0,0 +1,234 @@ + + + + + + +UmlClassDiagram + + + +4 + +TokenErrorReporter + +Internal: +    fail(err: Error, info: FailureInfo): uint +    failOpaque(err: Error, info: FailureInfo, opaqueError: uint): uint +Public: +    <<event>> Failure(error: uint, info: uint, detail: uint) + + + +7 + +CarefulMath + +Internal: +    mulUInt(a: uint, b: uint): (MathError, uint) +    divUInt(a: uint, b: uint): (MathError, uint) +    subUInt(a: uint, b: uint): (MathError, uint) +    addUInt(a: uint, b: uint): (MathError, uint) +    addThenSubUInt(a: uint, b: uint, c: uint): (MathError, uint) + + + +9 + +Exponential + +Public: +   expScale: uint +   halfExpScale: uint +   mantissaOne: uint + +Internal: +    getExp(num: uint, denom: uint): (MathError, Exp) +    addExp(a: Exp, b: Exp): (MathError, Exp) +    subExp(a: Exp, b: Exp): (MathError, Exp) +    mulScalar(a: Exp, scalar: uint): (MathError, Exp) +    mulScalarTruncate(a: Exp, scalar: uint): (MathError, uint) +    mulScalarTruncateAddUInt(a: Exp, scalar: uint, addend: uint): (MathError, uint) +    divScalar(a: Exp, scalar: uint): (MathError, Exp) +    divScalarByExp(scalar: uint, divisor: Exp): (MathError, Exp) +    divScalarByExpTruncate(scalar: uint, divisor: Exp): (MathError, uint) +    mulExp(a: Exp, b: Exp): (MathError, Exp) +    mulExp(a: uint, b: uint): (MathError, Exp) +    mulExp3(a: Exp, b: Exp, c: Exp): (MathError, Exp) +    divExp(a: Exp, b: Exp): (MathError, Exp) +    truncate(exp: Exp): uint +    lessThanExp(left: Exp, right: Exp): bool +    lessThanOrEqualExp(left: Exp, right: Exp): bool +    isZeroExp(value: Exp): bool + + + +9->7 + + + + + +13 + +ReentrancyGuard + +Private: +   _guardCounter: uint256 + +Public: +    <<modifier>> nonReentrant() +    constructor() + + + +15 + +<<Abstract>> +CToken + +Public: +   isCToken: bool +   name: string +   symbol: string +   decimals: uint +   borrowRateMaxMantissa: uint +   reserveFactorMaxMantissa: uint +   admin: address +   pendingAdmin: address +   comptroller: ComptrollerInterface +   interestRateModel: InterestRateModel +   initialExchangeRateMantissa: uint +   reserveFactorMantissa: uint +   accrualBlockNumber: uint +   borrowIndex: uint +   totalBorrows: uint +   totalReserves: uint +   totalSupply: uint256 +   accountTokens: mapping(address=>uint256) +   transferAllowances: mapping(address=>mapping(address=>uint256)) +   accountBorrows: mapping(address=>BorrowSnapshot) + +Internal: +    <<abstract>> getCashPrior(): uint +    <<abstract>> checkTransferIn(from: address, amount: uint): Error +    <<abstract>> doTransferIn(from: address, amount: uint): Error +    <<abstract>> doTransferOut(to: address, amount: uint): Error +    transferTokens(spender: address, src: address, dst: address, tokens: uint): uint +    getBlockNumber(): uint +    borrowBalanceStoredInternal(account: address): (MathError, uint) +    exchangeRateStoredInternal(): (MathError, uint) +    mintInternal(mintAmount: uint): uint <<nonReentrant>> +    mintFresh(minter: address, mintAmount: uint): uint +    redeemInternal(redeemTokens: uint): uint <<nonReentrant>> +    redeemUnderlyingInternal(redeemAmount: uint): uint <<nonReentrant>> +    redeemFresh(redeemer: address, redeemTokensIn: uint, redeemAmountIn: uint): uint +    borrowInternal(borrowAmount: uint): uint <<nonReentrant>> +    borrowFresh(borrower: address, borrowAmount: uint): uint +    repayBorrowInternal(repayAmount: uint): uint <<nonReentrant>> +    repayBorrowBehalfInternal(borrower: address, repayAmount: uint): uint <<nonReentrant>> +    repayBorrowFresh(payer: address, borrower: address, repayAmount: uint): uint +    liquidateBorrowInternal(borrower: address, repayAmount: uint, cTokenCollateral: CToken): uint <<nonReentrant>> +    liquidateBorrowFresh(liquidator: address, borrower: address, repayAmount: uint, cTokenCollateral: CToken): uint +    _setReserveFactorFresh(newReserveFactorMantissa: uint): uint +    _reduceReservesFresh(reduceAmount: uint): uint +    _setInterestRateModelFresh(newInterestRateModel: InterestRateModel): uint +External: +    transfer(dst: address, amount: uint256): bool <<nonReentrant>> +    transferFrom(src: address, dst: address, amount: uint256): bool <<nonReentrant>> +    approve(spender: address, amount: uint256): bool +    allowance(owner: address, spender: address): uint256 +    balanceOf(owner: address): uint256 +    balanceOfUnderlying(owner: address): uint +    getAccountSnapshot(account: address): (uint, uint, uint, uint) +    borrowRatePerBlock(): uint +    supplyRatePerBlock(): uint +    totalBorrowsCurrent(): uint <<nonReentrant>> +    borrowBalanceCurrent(account: address): uint <<nonReentrant>> +    getCash(): uint +    seize(liquidator: address, borrower: address, seizeTokens: uint): uint <<nonReentrant>> +    _setPendingAdmin(newPendingAdmin: address): uint +    _acceptAdmin(): uint +    _setReserveFactor(newReserveFactorMantissa: uint): uint <<nonReentrant>> +    _reduceReserves(reduceAmount: uint): uint <<nonReentrant>> +Public: +    <<event>> AccrueInterest(interestAccumulated: uint, borrowIndex: uint, totalBorrows: uint) +    <<event>> Mint(minter: address, mintAmount: uint, mintTokens: uint) +    <<event>> Redeem(redeemer: address, redeemAmount: uint, redeemTokens: uint) +    <<event>> Borrow(borrower: address, borrowAmount: uint, accountBorrows: uint, totalBorrows: uint) +    <<event>> RepayBorrow(payer: address, borrower: address, repayAmount: uint, accountBorrows: uint, totalBorrows: uint) +    <<event>> LiquidateBorrow(liquidator: address, borrower: address, repayAmount: uint, cTokenCollateral: address, seizeTokens: uint) +    <<event>> NewPendingAdmin(oldPendingAdmin: address, newPendingAdmin: address) +    <<event>> NewAdmin(oldAdmin: address, newAdmin: address) +    <<event>> NewComptroller(oldComptroller: ComptrollerInterface, newComptroller: ComptrollerInterface) +    <<event>> NewMarketInterestRateModel(oldInterestRateModel: InterestRateModel, newInterestRateModel: InterestRateModel) +    <<event>> NewReserveFactor(oldReserveFactorMantissa: uint, newReserveFactorMantissa: uint) +    <<event>> ReservesReduced(admin: address, reduceAmount: uint, newTotalReserves: uint) +    constructor(comptroller_: ComptrollerInterface, interestRateModel_: InterestRateModel, initialExchangeRateMantissa_: uint, name_: string, symbol_: string, decimals_: uint) +    borrowBalanceStored(account: address): uint +    exchangeRateCurrent(): uint <<nonReentrant>> +    exchangeRateStored(): uint +    accrueInterest(): uint +    _setComptroller(newComptroller: ComptrollerInterface): uint +    _setInterestRateModel(newInterestRateModel: InterestRateModel): uint + + + +15->4 + + + + + +15->9 + + + + + +15->13 + + + + + +15->15 + + + + + +22 + +CErc20 + +Public: +   underlying: address + +Internal: +    getCashPrior(): uint +    checkTransferIn(from: address, amount: uint): Error +    doTransferIn(from: address, amount: uint): Error +    doTransferOut(to: address, amount: uint): Error +External: +    mint(mintAmount: uint): uint +    redeem(redeemTokens: uint): uint +    redeemUnderlying(redeemAmount: uint): uint +    borrow(borrowAmount: uint): uint +    repayBorrow(repayAmount: uint): uint +    repayBorrowBehalf(borrower: address, repayAmount: uint): uint +    liquidateBorrow(borrower: address, repayAmount: uint, cTokenCollateral: CToken): uint +Public: +    constructor(underlying_: address, comptroller_: ComptrollerInterface, interestRateModel_: InterestRateModel, initialExchangeRateMantissa_: uint, name_: string, symbol_: string, decimals_: uint) + + + +22->15 + + + + + diff --git a/examples/CErc20.svg b/examples/CErc20.svg new file mode 100644 index 00000000..7fe7db9d --- /dev/null +++ b/examples/CErc20.svg @@ -0,0 +1,873 @@ + + + + + + +UmlClassDiagram + + + +0 + +<<Interface>> +ComptrollerInterface + +External: +     isComptroller(): bool +     enterMarkets(cTokens: address[]): uint[] +     exitMarket(cToken: address): uint +     mintAllowed(cToken: address, minter: address, mintAmount: uint): uint +     mintVerify(cToken: address, minter: address, mintAmount: uint, mintTokens: uint) +     redeemAllowed(cToken: address, redeemer: address, redeemTokens: uint): uint +     redeemVerify(cToken: address, redeemer: address, redeemAmount: uint, redeemTokens: uint) +     borrowAllowed(cToken: address, borrower: address, borrowAmount: uint): uint +     borrowVerify(cToken: address, borrower: address, borrowAmount: uint) +     repayBorrowAllowed(cToken: address, payer: address, borrower: address, repayAmount: uint): uint +     repayBorrowVerify(cToken: address, payer: address, borrower: address, repayAmount: uint, borrowerIndex: uint) +     liquidateBorrowAllowed(cTokenBorrowed: address, cTokenCollateral: address, liquidator: address, borrower: address, repayAmount: uint): uint +     liquidateBorrowVerify(cTokenBorrowed: address, cTokenCollateral: address, liquidator: address, borrower: address, repayAmount: uint, seizeTokens: uint) +     seizeAllowed(cTokenCollateral: address, cTokenBorrowed: address, liquidator: address, borrower: address, seizeTokens: uint): uint +     seizeVerify(cTokenCollateral: address, cTokenBorrowed: address, liquidator: address, borrower: address, seizeTokens: uint) +     transferAllowed(cToken: address, src: address, dst: address, transferTokens: uint): uint +     transferVerify(cToken: address, src: address, dst: address, transferTokens: uint) +     liquidateCalculateSeizeTokens(cTokenBorrowed: address, cTokenCollateral: address, repayAmount: uint): (uint, uint) + + + +2 + +<<Enum>> +Error + +NO_ERROR: 0 +UNAUTHORIZED: 1 +COMPTROLLER_MISMATCH: 2 +INSUFFICIENT_SHORTFALL: 3 +INSUFFICIENT_LIQUIDITY: 4 +INVALID_CLOSE_FACTOR: 5 +INVALID_COLLATERAL_FACTOR: 6 +INVALID_LIQUIDATION_INCENTIVE: 7 +MARKET_NOT_ENTERED: 8 +MARKET_NOT_LISTED: 9 +MARKET_ALREADY_LISTED: 10 +MATH_ERROR: 11 +NONZERO_BORROW_BALANCE: 12 +PRICE_ERROR: 13 +REJECTION: 14 +SNAPSHOT_ERROR: 15 +TOO_MANY_ASSETS: 16 +TOO_MUCH_REPAY: 17 + + + +1 + +ComptrollerErrorReporter + +Internal: +    fail(err: Error, info: FailureInfo): uint +    failOpaque(err: Error, info: FailureInfo, opaqueError: uint): uint +Public: +    <<event>> Failure(error: uint, info: uint, detail: uint) + + + +2->1 + + + + + +3 + +<<Enum>> +FailureInfo + +ACCEPT_ADMIN_PENDING_ADMIN_CHECK: 0 +ACCEPT_PENDING_IMPLEMENTATION_ADDRESS_CHECK: 1 +EXIT_MARKET_BALANCE_OWED: 2 +EXIT_MARKET_REJECTION: 3 +SET_CLOSE_FACTOR_OWNER_CHECK: 4 +SET_CLOSE_FACTOR_VALIDATION: 5 +SET_COLLATERAL_FACTOR_OWNER_CHECK: 6 +SET_COLLATERAL_FACTOR_NO_EXISTS: 7 +SET_COLLATERAL_FACTOR_VALIDATION: 8 +SET_COLLATERAL_FACTOR_WITHOUT_PRICE: 9 +SET_IMPLEMENTATION_OWNER_CHECK: 10 +SET_LIQUIDATION_INCENTIVE_OWNER_CHECK: 11 +SET_LIQUIDATION_INCENTIVE_VALIDATION: 12 +SET_MAX_ASSETS_OWNER_CHECK: 13 +SET_PENDING_ADMIN_OWNER_CHECK: 14 +SET_PENDING_IMPLEMENTATION_OWNER_CHECK: 15 +SET_PRICE_ORACLE_OWNER_CHECK: 16 +SUPPORT_MARKET_EXISTS: 17 +SUPPORT_MARKET_OWNER_CHECK: 18 +ZUNUSED: 19 + + + +3->1 + + + + + +1->2 + + + + + +1->3 + + + + + +5 + +<<Enum>> +Error + +NO_ERROR: 0 +UNAUTHORIZED: 1 +BAD_INPUT: 2 +COMPTROLLER_REJECTION: 3 +COMPTROLLER_CALCULATION_ERROR: 4 +INTEREST_RATE_MODEL_ERROR: 5 +INVALID_ACCOUNT_PAIR: 6 +INVALID_CLOSE_AMOUNT_REQUESTED: 7 +INVALID_COLLATERAL_FACTOR: 8 +MATH_ERROR: 9 +MARKET_NOT_FRESH: 10 +MARKET_NOT_LISTED: 11 +TOKEN_INSUFFICIENT_ALLOWANCE: 12 +TOKEN_INSUFFICIENT_BALANCE: 13 +TOKEN_INSUFFICIENT_CASH: 14 +TOKEN_TRANSFER_IN_FAILED: 15 +TOKEN_TRANSFER_OUT_FAILED: 16 + + + +4 + +TokenErrorReporter + +Internal: +    fail(err: Error, info: FailureInfo): uint +    failOpaque(err: Error, info: FailureInfo, opaqueError: uint): uint +Public: +    <<event>> Failure(error: uint, info: uint, detail: uint) + + + +5->4 + + + + + +6 + +<<Enum>> +FailureInfo + +ACCEPT_ADMIN_PENDING_ADMIN_CHECK: 0 +ACCRUE_INTEREST_ACCUMULATED_INTEREST_CALCULATION_FAILED: 1 +ACCRUE_INTEREST_BORROW_RATE_CALCULATION_FAILED: 2 +ACCRUE_INTEREST_NEW_BORROW_INDEX_CALCULATION_FAILED: 3 +ACCRUE_INTEREST_NEW_TOTAL_BORROWS_CALCULATION_FAILED: 4 +ACCRUE_INTEREST_NEW_TOTAL_RESERVES_CALCULATION_FAILED: 5 +ACCRUE_INTEREST_SIMPLE_INTEREST_FACTOR_CALCULATION_FAILED: 6 +BORROW_ACCUMULATED_BALANCE_CALCULATION_FAILED: 7 +BORROW_ACCRUE_INTEREST_FAILED: 8 +BORROW_CASH_NOT_AVAILABLE: 9 +BORROW_FRESHNESS_CHECK: 10 +BORROW_NEW_TOTAL_BALANCE_CALCULATION_FAILED: 11 +BORROW_NEW_ACCOUNT_BORROW_BALANCE_CALCULATION_FAILED: 12 +BORROW_MARKET_NOT_LISTED: 13 +BORROW_COMPTROLLER_REJECTION: 14 +LIQUIDATE_ACCRUE_BORROW_INTEREST_FAILED: 15 +LIQUIDATE_ACCRUE_COLLATERAL_INTEREST_FAILED: 16 +LIQUIDATE_COLLATERAL_FRESHNESS_CHECK: 17 +LIQUIDATE_COMPTROLLER_REJECTION: 18 +LIQUIDATE_COMPTROLLER_CALCULATE_AMOUNT_SEIZE_FAILED: 19 +LIQUIDATE_CLOSE_AMOUNT_IS_UINT_MAX: 20 +LIQUIDATE_CLOSE_AMOUNT_IS_ZERO: 21 +LIQUIDATE_FRESHNESS_CHECK: 22 +LIQUIDATE_LIQUIDATOR_IS_BORROWER: 23 +LIQUIDATE_REPAY_BORROW_FRESH_FAILED: 24 +LIQUIDATE_SEIZE_BALANCE_INCREMENT_FAILED: 25 +LIQUIDATE_SEIZE_BALANCE_DECREMENT_FAILED: 26 +LIQUIDATE_SEIZE_COMPTROLLER_REJECTION: 27 +LIQUIDATE_SEIZE_LIQUIDATOR_IS_BORROWER: 28 +LIQUIDATE_SEIZE_TOO_MUCH: 29 +MINT_ACCRUE_INTEREST_FAILED: 30 +MINT_COMPTROLLER_REJECTION: 31 +MINT_EXCHANGE_CALCULATION_FAILED: 32 +MINT_EXCHANGE_RATE_READ_FAILED: 33 +MINT_FRESHNESS_CHECK: 34 +MINT_NEW_ACCOUNT_BALANCE_CALCULATION_FAILED: 35 +MINT_NEW_TOTAL_SUPPLY_CALCULATION_FAILED: 36 +MINT_TRANSFER_IN_FAILED: 37 +MINT_TRANSFER_IN_NOT_POSSIBLE: 38 +REDEEM_ACCRUE_INTEREST_FAILED: 39 +REDEEM_COMPTROLLER_REJECTION: 40 +REDEEM_EXCHANGE_TOKENS_CALCULATION_FAILED: 41 +REDEEM_EXCHANGE_AMOUNT_CALCULATION_FAILED: 42 +REDEEM_EXCHANGE_RATE_READ_FAILED: 43 +REDEEM_FRESHNESS_CHECK: 44 +REDEEM_NEW_ACCOUNT_BALANCE_CALCULATION_FAILED: 45 +REDEEM_NEW_TOTAL_SUPPLY_CALCULATION_FAILED: 46 +REDEEM_TRANSFER_OUT_NOT_POSSIBLE: 47 +REDUCE_RESERVES_ACCRUE_INTEREST_FAILED: 48 +REDUCE_RESERVES_ADMIN_CHECK: 49 +REDUCE_RESERVES_CASH_NOT_AVAILABLE: 50 +REDUCE_RESERVES_FRESH_CHECK: 51 +REDUCE_RESERVES_VALIDATION: 52 +REPAY_BEHALF_ACCRUE_INTEREST_FAILED: 53 +REPAY_BORROW_ACCRUE_INTEREST_FAILED: 54 +REPAY_BORROW_ACCUMULATED_BALANCE_CALCULATION_FAILED: 55 +REPAY_BORROW_COMPTROLLER_REJECTION: 56 +REPAY_BORROW_FRESHNESS_CHECK: 57 +REPAY_BORROW_NEW_ACCOUNT_BORROW_BALANCE_CALCULATION_FAILED: 58 +REPAY_BORROW_NEW_TOTAL_BALANCE_CALCULATION_FAILED: 59 +REPAY_BORROW_TRANSFER_IN_NOT_POSSIBLE: 60 +SET_COLLATERAL_FACTOR_OWNER_CHECK: 61 +SET_COLLATERAL_FACTOR_VALIDATION: 62 +SET_COMPTROLLER_OWNER_CHECK: 63 +SET_INTEREST_RATE_MODEL_ACCRUE_INTEREST_FAILED: 64 +SET_INTEREST_RATE_MODEL_FRESH_CHECK: 65 +SET_INTEREST_RATE_MODEL_OWNER_CHECK: 66 +SET_MAX_ASSETS_OWNER_CHECK: 67 +SET_ORACLE_MARKET_NOT_LISTED: 68 +SET_PENDING_ADMIN_OWNER_CHECK: 69 +SET_RESERVE_FACTOR_ACCRUE_INTEREST_FAILED: 70 +SET_RESERVE_FACTOR_ADMIN_CHECK: 71 +SET_RESERVE_FACTOR_FRESH_CHECK: 72 +SET_RESERVE_FACTOR_BOUNDS_CHECK: 73 +TRANSFER_COMPTROLLER_REJECTION: 74 +TRANSFER_NOT_ALLOWED: 75 +TRANSFER_NOT_ENOUGH: 76 +TRANSFER_TOO_MUCH: 77 + + + +6->4 + + + + + +4->2 + + + + + +4->3 + + + + + +8 + +<<Enum>> +MathError + +NO_ERROR: 0 +DIVISION_BY_ZERO: 1 +INTEGER_OVERFLOW: 2 +INTEGER_UNDERFLOW: 3 + + + +7 + +CarefulMath + +Internal: +    mulUInt(a: uint, b: uint): (MathError, uint) +    divUInt(a: uint, b: uint): (MathError, uint) +    subUInt(a: uint, b: uint): (MathError, uint) +    addUInt(a: uint, b: uint): (MathError, uint) +    addThenSubUInt(a: uint, b: uint, c: uint): (MathError, uint) + + + +8->7 + + + + + +7->8 + + + + + +10 + +<<Struct>> +Exp + +mantissa: uint + + + +9 + +Exponential + +Public: +   expScale: uint +   halfExpScale: uint +   mantissaOne: uint + +Internal: +    getExp(num: uint, denom: uint): (MathError, Exp) +    addExp(a: Exp, b: Exp): (MathError, Exp) +    subExp(a: Exp, b: Exp): (MathError, Exp) +    mulScalar(a: Exp, scalar: uint): (MathError, Exp) +    mulScalarTruncate(a: Exp, scalar: uint): (MathError, uint) +    mulScalarTruncateAddUInt(a: Exp, scalar: uint, addend: uint): (MathError, uint) +    divScalar(a: Exp, scalar: uint): (MathError, Exp) +    divScalarByExp(scalar: uint, divisor: Exp): (MathError, Exp) +    divScalarByExpTruncate(scalar: uint, divisor: Exp): (MathError, uint) +    mulExp(a: Exp, b: Exp): (MathError, Exp) +    mulExp(a: uint, b: uint): (MathError, Exp) +    mulExp3(a: Exp, b: Exp, c: Exp): (MathError, Exp) +    divExp(a: Exp, b: Exp): (MathError, Exp) +    truncate(exp: Exp): uint +    lessThanExp(left: Exp, right: Exp): bool +    lessThanOrEqualExp(left: Exp, right: Exp): bool +    isZeroExp(value: Exp): bool + + + +10->9 + + + + + +9->8 + + + + + +9->7 + + + + + +9->10 + + + + + +11 + +<<Interface>> +EIP20Interface + +External: +     totalSupply(): uint256 +     balanceOf(owner: address): (balance: uint256) +     transfer(dst: address, amount: uint256): (success: bool) +     transferFrom(src: address, dst: address, amount: uint256): (success: bool) +     approve(spender: address, amount: uint256): (success: bool) +     allowance(owner: address, spender: address): (remaining: uint256) +Public: +    <<event>> Transfer(from: address, to: address, amount: uint256) +    <<event>> Approval(owner: address, spender: address, amount: uint256) + + + +12 + +<<Interface>> +EIP20NonStandardInterface + +External: +     totalSupply(): uint256 +     balanceOf(owner: address): (balance: uint256) +     transfer(dst: address, amount: uint256) +     transferFrom(src: address, dst: address, amount: uint256) +     approve(spender: address, amount: uint256): (success: bool) +     allowance(owner: address, spender: address): (remaining: uint256) +Public: +    <<event>> Transfer(from: address, to: address, amount: uint256) +    <<event>> Approval(owner: address, spender: address, amount: uint256) + + + +13 + +ReentrancyGuard + +Private: +   _guardCounter: uint256 + +Public: +    <<modifier>> nonReentrant() +    constructor() + + + +14 + +<<Interface>> +InterestRateModel + +External: +     getBorrowRate(cash: uint, borrows: uint, reserves: uint): (uint, uint) +     isInterestRateModel(): bool + + + +16 + +<<Struct>> +BorrowSnapshot + +principal: uint +interestIndex: uint + + + +15 + +<<Abstract>> +CToken + +Public: +   isCToken: bool +   name: string +   symbol: string +   decimals: uint +   borrowRateMaxMantissa: uint +   reserveFactorMaxMantissa: uint +   admin: address +   pendingAdmin: address +   comptroller: ComptrollerInterface +   interestRateModel: InterestRateModel +   initialExchangeRateMantissa: uint +   reserveFactorMantissa: uint +   accrualBlockNumber: uint +   borrowIndex: uint +   totalBorrows: uint +   totalReserves: uint +   totalSupply: uint256 +   accountTokens: mapping(address=>uint256) +   transferAllowances: mapping(address=>mapping(address=>uint256)) +   accountBorrows: mapping(address=>BorrowSnapshot) + +Internal: +    <<abstract>> getCashPrior(): uint +    <<abstract>> checkTransferIn(from: address, amount: uint): Error +    <<abstract>> doTransferIn(from: address, amount: uint): Error +    <<abstract>> doTransferOut(to: address, amount: uint): Error +    transferTokens(spender: address, src: address, dst: address, tokens: uint): uint +    getBlockNumber(): uint +    borrowBalanceStoredInternal(account: address): (MathError, uint) +    exchangeRateStoredInternal(): (MathError, uint) +    mintInternal(mintAmount: uint): uint <<nonReentrant>> +    mintFresh(minter: address, mintAmount: uint): uint +    redeemInternal(redeemTokens: uint): uint <<nonReentrant>> +    redeemUnderlyingInternal(redeemAmount: uint): uint <<nonReentrant>> +    redeemFresh(redeemer: address, redeemTokensIn: uint, redeemAmountIn: uint): uint +    borrowInternal(borrowAmount: uint): uint <<nonReentrant>> +    borrowFresh(borrower: address, borrowAmount: uint): uint +    repayBorrowInternal(repayAmount: uint): uint <<nonReentrant>> +    repayBorrowBehalfInternal(borrower: address, repayAmount: uint): uint <<nonReentrant>> +    repayBorrowFresh(payer: address, borrower: address, repayAmount: uint): uint +    liquidateBorrowInternal(borrower: address, repayAmount: uint, cTokenCollateral: CToken): uint <<nonReentrant>> +    liquidateBorrowFresh(liquidator: address, borrower: address, repayAmount: uint, cTokenCollateral: CToken): uint +    _setReserveFactorFresh(newReserveFactorMantissa: uint): uint +    _reduceReservesFresh(reduceAmount: uint): uint +    _setInterestRateModelFresh(newInterestRateModel: InterestRateModel): uint +External: +    transfer(dst: address, amount: uint256): bool <<nonReentrant>> +    transferFrom(src: address, dst: address, amount: uint256): bool <<nonReentrant>> +    approve(spender: address, amount: uint256): bool +    allowance(owner: address, spender: address): uint256 +    balanceOf(owner: address): uint256 +    balanceOfUnderlying(owner: address): uint +    getAccountSnapshot(account: address): (uint, uint, uint, uint) +    borrowRatePerBlock(): uint +    supplyRatePerBlock(): uint +    totalBorrowsCurrent(): uint <<nonReentrant>> +    borrowBalanceCurrent(account: address): uint <<nonReentrant>> +    getCash(): uint +    seize(liquidator: address, borrower: address, seizeTokens: uint): uint <<nonReentrant>> +    _setPendingAdmin(newPendingAdmin: address): uint +    _acceptAdmin(): uint +    _setReserveFactor(newReserveFactorMantissa: uint): uint <<nonReentrant>> +    _reduceReserves(reduceAmount: uint): uint <<nonReentrant>> +Public: +    <<event>> AccrueInterest(interestAccumulated: uint, borrowIndex: uint, totalBorrows: uint) +    <<event>> Mint(minter: address, mintAmount: uint, mintTokens: uint) +    <<event>> Redeem(redeemer: address, redeemAmount: uint, redeemTokens: uint) +    <<event>> Borrow(borrower: address, borrowAmount: uint, accountBorrows: uint, totalBorrows: uint) +    <<event>> RepayBorrow(payer: address, borrower: address, repayAmount: uint, accountBorrows: uint, totalBorrows: uint) +    <<event>> LiquidateBorrow(liquidator: address, borrower: address, repayAmount: uint, cTokenCollateral: address, seizeTokens: uint) +    <<event>> NewPendingAdmin(oldPendingAdmin: address, newPendingAdmin: address) +    <<event>> NewAdmin(oldAdmin: address, newAdmin: address) +    <<event>> NewComptroller(oldComptroller: ComptrollerInterface, newComptroller: ComptrollerInterface) +    <<event>> NewMarketInterestRateModel(oldInterestRateModel: InterestRateModel, newInterestRateModel: InterestRateModel) +    <<event>> NewReserveFactor(oldReserveFactorMantissa: uint, newReserveFactorMantissa: uint) +    <<event>> ReservesReduced(admin: address, reduceAmount: uint, newTotalReserves: uint) +    constructor(comptroller_: ComptrollerInterface, interestRateModel_: InterestRateModel, initialExchangeRateMantissa_: uint, name_: string, symbol_: string, decimals_: uint) +    borrowBalanceStored(account: address): uint +    exchangeRateCurrent(): uint <<nonReentrant>> +    exchangeRateStored(): uint +    accrueInterest(): uint +    _setComptroller(newComptroller: ComptrollerInterface): uint +    _setInterestRateModel(newInterestRateModel: InterestRateModel): uint + + + +16->15 + + + + + +17 + +<<Struct>> +AccrueInterestLocalVars + +mathErr: MathError +opaqueErr: uint +borrowRateMantissa: uint +currentBlockNumber: uint +blockDelta: uint +simpleInterestFactor: Exp +interestAccumulated: uint +totalBorrowsNew: uint +totalReservesNew: uint +borrowIndexNew: uint + + + +17->8 + + + + + +17->10 + + + + + +17->15 + + + + + +18 + +<<Struct>> +MintLocalVars + +err: Error +mathErr: MathError +exchangeRateMantissa: uint +mintTokens: uint +totalSupplyNew: uint +accountTokensNew: uint + + + +18->2 + + + + + +18->8 + + + + + +18->15 + + + + + +19 + +<<Struct>> +RedeemLocalVars + +err: Error +mathErr: MathError +exchangeRateMantissa: uint +redeemTokens: uint +redeemAmount: uint +totalSupplyNew: uint +accountTokensNew: uint + + + +19->2 + + + + + +19->8 + + + + + +19->15 + + + + + +20 + +<<Struct>> +BorrowLocalVars + +err: Error +mathErr: MathError +accountBorrows: uint +accountBorrowsNew: uint +totalBorrowsNew: uint + + + +20->2 + + + + + +20->8 + + + + + +20->15 + + + + + +21 + +<<Struct>> +RepayBorrowLocalVars + +err: Error +mathErr: MathError +repayAmount: uint +borrowerIndex: uint +accountBorrows: uint +accountBorrowsNew: uint +totalBorrowsNew: uint + + + +21->2 + + + + + +21->8 + + + + + +21->15 + + + + + +15->0 + + + + + +15->2 + + + + + +15->3 + + + + + +15->4 + + + + + +15->8 + + + + + +15->10 + + + + + +15->9 + + + + + +15->11 + + + + + +15->13 + + + + + +15->14 + + + + + +15->16 + + + + + +15->17 + + + + + +15->18 + + + + + +15->19 + + + + + +15->20 + + + + + +15->21 + + + + + +15->15 + + + + + +22 + +CErc20 + +Public: +   underlying: address + +Internal: +    getCashPrior(): uint +    checkTransferIn(from: address, amount: uint): Error +    doTransferIn(from: address, amount: uint): Error +    doTransferOut(to: address, amount: uint): Error +External: +    mint(mintAmount: uint): uint +    redeem(redeemTokens: uint): uint +    redeemUnderlying(redeemAmount: uint): uint +    borrow(borrowAmount: uint): uint +    repayBorrow(repayAmount: uint): uint +    repayBorrowBehalf(borrower: address, repayAmount: uint): uint +    liquidateBorrow(borrower: address, repayAmount: uint, cTokenCollateral: CToken): uint +Public: +    constructor(underlying_: address, comptroller_: ComptrollerInterface, interestRateModel_: InterestRateModel, initialExchangeRateMantissa_: uint, name_: string, symbol_: string, decimals_: uint) + + + +22->0 + + + + + +22->2 + + + + + +22->11 + + + + + +22->12 + + + + + +22->14 + + + + + +22->15 + + + + + diff --git a/examples/README.md b/examples/README.md index 228653b4..3fd13074 100644 --- a/examples/README.md +++ b/examples/README.md @@ -39,62 +39,74 @@ sol2uml -n bsc 0xB07c1C479b2Fdeb9f9B2d02300C13b328BF86d65 ![Open Zeppelin ERC20](./OpenZeppelinAll.svg) Generated from version [4.7.3 contracts](https://github.com/OpenZeppelin/openzeppelin-contracts/tree/v4.7.3/contracts) -## MakerDAO's SAI Token +## Uniswap V3 Router -![MakerDAO](./MakerDAO_SAI.svg) -Generated from GitHub commit [84c682eeb4e27264503370ef5aafcb9ee3217acb](https://github.com/makerdao/sai/tree/84c682eeb4e27264503370ef5aafcb9ee3217acb/src) of makerdao/sai/src +* -hp hide private and internal variables and functions +* -hi hide interfaces +* -hl hide libraries +* -he hide enums -## Tether - -![Tether](./tether.svg) +![Uniswap V3 Router](./uniswap-router.svg) Generated from running - ``` -sol2uml 0xdAC17F958D2ee523a2206206994597C13D831ec7 +sol2uml -hp -hi -hl -hs -he 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45 ``` -This uses the verified Solidity code loaded to Etherscan https://etherscan.io/address/0xdac17f958d2ee523a2206206994597c13d831ec7#code +This uses the verified Solidity code loaded to Etherscan https://etherscan.io/address/0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45#code -## 0x -![0x Protocol v2 Exchange](./0xv2.svg) -Generated from running +## Uniswap V3 Router Squashed + +Same as the previous diagram but the inherited contracts are squashed into a single class diagram with the `-s, --squash` option. + +The last stereotype is the contract the variable or function is implemented in. For example, `unwrapWETH9` is implemented in the `PeripheryPaymentsWithFeeExtended` contract. +![Uniswap V3 Router Squashed](./uniswap-router-squash.svg) + +Generated from running ``` -sol2uml 0x4F833a24e1f95D70F028921e27040Ca56E09AB0b +sol2uml -s -hp -hi -hl -hs -he 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45 ``` -This uses the verified Solidity code loaded to Etherscan https://etherscan.io/address/0x4F833a24e1f95D70F028921e27040Ca56E09AB0b#code +## Uniswap V3 Router Squashed No Source -## Compound Finance's cDAI +Adding the `-hsc, --hideSourceContract` option to the previous diagram removes the stereotype with the source contract the variable or function was implemented in. -![Compound Finance cDAI](./cDAI.svg) -Generated from running +![Uniswap V3 Router Squashed no source contract](./uniswap-router-squash-no-source.svg) +Generated from running ``` -sol2uml 0xf5dce57282a584d2746faf1593d3121fcac444dc +sol2uml -s -hsc -hp -hi -hl -hs -he 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45 ``` -This uses the verified Solidity code loaded to Etherscan https://etherscan.io/address/0xf5dce57282a584d2746faf1593d3121fcac444dc#code - -## Chainlink +## Tether -![Chainlink](./chainlink.svg) +![Tether](./tether.svg) Generated from running -```bash -sol2uml 0x79fEbF6B9F76853EDBcBc913e6aAE8232cFB9De9 +``` +sol2uml 0xdAC17F958D2ee523a2206206994597C13D831ec7 ``` -This uses the verified Solidity code loaded to Etherscan https://etherscan.io/address/0x79fEbF6B9F76853EDBcBc913e6aAE8232cFB9De9#code +This uses the verified Solidity code loaded to Etherscan https://etherscan.io/address/0xdac17f958d2ee523a2206206994597c13d831ec7#code -## Augur +## Compound Finance's cDAI -![Augur](./augur.svg) +![Compound Finance cDAI](./CErc20.svg) Generated from running - -```bash -sol2uml 0x7F27B0598949DbF9e539BBD217f15BF3F5E97999 ``` +sol2uml 0xf5dce57282a584d2746faf1593d3121fcac444dc +``` + +This uses the verified Solidity code loaded to Etherscan https://etherscan.io/address/0xf5dce57282a584d2746faf1593d3121fcac444dc#code + +## Compound Finance's cDAI Hide -This uses the verified Solidity code loaded to Etherscan https://etherscan.io/address/0x7F27B0598949DbF9e539BBD217f15BF3F5E97999#code +Same as the previous except enums, stucts and interfaces are hidden. +Also, only classes linked to the base `CErc20` contract are included. + +![Compound Finance cDAI](./CErc20-hide.svg) +Generated from running +``` +sol2uml -b CErc20 -he -hs -hi 0xf5dce57282a584d2746faf1593d3121fcac444dc +``` diff --git a/examples/uniswap-router-squash-no-source.svg b/examples/uniswap-router-squash-no-source.svg new file mode 100644 index 00000000..54285b7c --- /dev/null +++ b/examples/uniswap-router-squash-no-source.svg @@ -0,0 +1,69 @@ + + + + + + +UmlClassDiagram + + + +0 + +SwapRouter02 +contracts/SwapRouter02.sol + +Public: +   factoryV2: address +   positionManager: address +   factory: address +   WETH9: address + +External: +    <<payable>> null() +    <<payable>> refundETH() +    <<payable>> unwrapWETH9(amountMinimum: uint256) +    <<payable>> wrapETH(value: uint256) +    <<payable>> sweepToken(token: address, amountMinimum: uint256) +    <<payable>> pull(token: address, value: uint256) +    <<payable>> unwrapWETH9WithFee(amountMinimum: uint256, feeBips: uint256, feeRecipient: address) +    <<payable>> sweepTokenWithFee(token: address, amountMinimum: uint256, feeBips: uint256, feeRecipient: address) +    <<payable>> swapExactTokensForTokens(amountIn: uint256, amountOutMin: uint256, path: address[], to: address): (amountOut: uint256) +    <<payable>> swapTokensForExactTokens(amountOut: uint256, amountInMax: uint256, path: address[], to: address): (amountIn: uint256) +    <<payable>> exactInputSingle(params: ExactInputSingleParams): (amountOut: uint256) +    <<payable>> exactInput(params: ExactInputParams): (amountOut: uint256) +    <<payable>> exactOutputSingle(params: ExactOutputSingleParams): (amountIn: uint256) +    <<payable>> exactOutput(params: ExactOutputParams): (amountIn: uint256) +    <<payable>> approveMax(token: address) +    <<payable>> approveMaxMinusOne(token: address) +    <<payable>> approveZeroThenMax(token: address) +    <<payable>> approveZeroThenMaxMinusOne(token: address) +    <<payable>> mint(params: MintParams): (result: bytes) +    <<payable>> increaseLiquidity(params: IncreaseLiquidityParams): (result: bytes) +    <<payable>> multicall(deadline: uint256, data: bytes[]): bytes[] <<checkDeadline>> +    <<payable>> multicall(previousBlockhash: bytes32, data: bytes[]): bytes[] <<checkPreviousBlockhash>> +    <<payable>> selfPermitIfNecessary(token: address, value: uint256, deadline: uint256, v: uint8, r: bytes32, s: bytes32) +    <<payable>> selfPermitAllowedIfNecessary(token: address, nonce: uint256, expiry: uint256, v: uint8, r: bytes32, s: bytes32) +    checkOracleSlippage(path: bytes, maximumTickDivergence: uint24, secondsAgo: uint32) +    checkOracleSlippage(paths: bytes[], amounts: uint128[], maximumTickDivergence: uint24, secondsAgo: uint32) +    uniswapV3SwapCallback(amount0Delta: int256, amount1Delta: int256, _data: bytes) +    getApprovalType(token: address, amount: uint256): ApprovalType +Public: +    <<payable>> unwrapWETH9(amountMinimum: uint256, recipient: address) +    <<payable>> sweepToken(token: address, amountMinimum: uint256, recipient: address) +    <<payable>> unwrapWETH9WithFee(amountMinimum: uint256, recipient: address, feeBips: uint256, feeRecipient: address) +    <<payable>> sweepTokenWithFee(token: address, amountMinimum: uint256, recipient: address, feeBips: uint256, feeRecipient: address) +    <<payable>> callPositionManager(data: bytes): (result: bytes) +    <<payable>> multicall(data: bytes[]): (results: bytes[]) +    <<payable>> selfPermit(token: address, value: uint256, deadline: uint256, v: uint8, r: bytes32, s: bytes32) +    <<payable>> selfPermitAllowed(token: address, nonce: uint256, expiry: uint256, v: uint8, r: bytes32, s: bytes32) +    <<modifier>> checkDeadline(deadline: uint256) +    <<modifier>> checkPreviousBlockhash(previousBlockhash: bytes32) +    constructor(_factory: address, _WETH9: address) +    constructor(_factoryV2: address, factoryV3: address, _positionManager: address, _WETH9: address) + + + diff --git a/examples/uniswap-router-squash.svg b/examples/uniswap-router-squash.svg new file mode 100644 index 00000000..2faaaed1 --- /dev/null +++ b/examples/uniswap-router-squash.svg @@ -0,0 +1,69 @@ + + + + + + +UmlClassDiagram + + + +0 + +SwapRouter02 +contracts/SwapRouter02.sol + +Public: +   factoryV2: address <<ImmutableState>> +   positionManager: address <<ImmutableState>> +   factory: address <<PeripheryImmutableState>> +   WETH9: address <<PeripheryImmutableState>> + +External: +    <<payable>> null() <<PeripheryPayments>> +    <<payable>> refundETH() <<PeripheryPayments>> +    <<payable>> unwrapWETH9(amountMinimum: uint256) <<PeripheryPaymentsExtended>> +    <<payable>> wrapETH(value: uint256) <<PeripheryPaymentsExtended>> +    <<payable>> sweepToken(token: address, amountMinimum: uint256) <<PeripheryPaymentsExtended>> +    <<payable>> pull(token: address, value: uint256) <<PeripheryPaymentsExtended>> +    <<payable>> unwrapWETH9WithFee(amountMinimum: uint256, feeBips: uint256, feeRecipient: address) <<PeripheryPaymentsWithFeeExtended>> +    <<payable>> sweepTokenWithFee(token: address, amountMinimum: uint256, feeBips: uint256, feeRecipient: address) <<PeripheryPaymentsWithFeeExtended>> +    <<payable>> swapExactTokensForTokens(amountIn: uint256, amountOutMin: uint256, path: address[], to: address): (amountOut: uint256) <<V2SwapRouter>> +    <<payable>> swapTokensForExactTokens(amountOut: uint256, amountInMax: uint256, path: address[], to: address): (amountIn: uint256) <<V2SwapRouter>> +    <<payable>> exactInputSingle(params: ExactInputSingleParams): (amountOut: uint256) <<V3SwapRouter>> +    <<payable>> exactInput(params: ExactInputParams): (amountOut: uint256) <<V3SwapRouter>> +    <<payable>> exactOutputSingle(params: ExactOutputSingleParams): (amountIn: uint256) <<V3SwapRouter>> +    <<payable>> exactOutput(params: ExactOutputParams): (amountIn: uint256) <<V3SwapRouter>> +    <<payable>> approveMax(token: address) <<ApproveAndCall>> +    <<payable>> approveMaxMinusOne(token: address) <<ApproveAndCall>> +    <<payable>> approveZeroThenMax(token: address) <<ApproveAndCall>> +    <<payable>> approveZeroThenMaxMinusOne(token: address) <<ApproveAndCall>> +    <<payable>> mint(params: MintParams): (result: bytes) <<ApproveAndCall>> +    <<payable>> increaseLiquidity(params: IncreaseLiquidityParams): (result: bytes) <<ApproveAndCall>> +    <<payable>> multicall(deadline: uint256, data: bytes[]): bytes[] <<checkDeadline>> <<MulticallExtended>> +    <<payable>> multicall(previousBlockhash: bytes32, data: bytes[]): bytes[] <<checkPreviousBlockhash>> <<MulticallExtended>> +    <<payable>> selfPermitIfNecessary(token: address, value: uint256, deadline: uint256, v: uint8, r: bytes32, s: bytes32) <<SelfPermit>> +    <<payable>> selfPermitAllowedIfNecessary(token: address, nonce: uint256, expiry: uint256, v: uint8, r: bytes32, s: bytes32) <<SelfPermit>> +    checkOracleSlippage(path: bytes, maximumTickDivergence: uint24, secondsAgo: uint32) <<OracleSlippage>> +    checkOracleSlippage(paths: bytes[], amounts: uint128[], maximumTickDivergence: uint24, secondsAgo: uint32) <<OracleSlippage>> +    uniswapV3SwapCallback(amount0Delta: int256, amount1Delta: int256, _data: bytes) <<V3SwapRouter>> +    getApprovalType(token: address, amount: uint256): ApprovalType <<ApproveAndCall>> +Public: +    <<payable>> unwrapWETH9(amountMinimum: uint256, recipient: address) <<PeripheryPayments>> +    <<payable>> sweepToken(token: address, amountMinimum: uint256, recipient: address) <<PeripheryPayments>> +    <<payable>> unwrapWETH9WithFee(amountMinimum: uint256, recipient: address, feeBips: uint256, feeRecipient: address) <<PeripheryPaymentsWithFee>> +    <<payable>> sweepTokenWithFee(token: address, amountMinimum: uint256, recipient: address, feeBips: uint256, feeRecipient: address) <<PeripheryPaymentsWithFee>> +    <<payable>> callPositionManager(data: bytes): (result: bytes) <<ApproveAndCall>> +    <<payable>> multicall(data: bytes[]): (results: bytes[]) <<Multicall>> +    <<payable>> selfPermit(token: address, value: uint256, deadline: uint256, v: uint8, r: bytes32, s: bytes32) <<SelfPermit>> +    <<payable>> selfPermitAllowed(token: address, nonce: uint256, expiry: uint256, v: uint8, r: bytes32, s: bytes32) <<SelfPermit>> +    <<modifier>> checkDeadline(deadline: uint256) <<PeripheryValidation>> +    <<modifier>> checkPreviousBlockhash(previousBlockhash: bytes32) <<PeripheryValidationExtended>> +    constructor(_factory: address, _WETH9: address) <<PeripheryImmutableState>> +    constructor(_factoryV2: address, factoryV3: address, _positionManager: address, _WETH9: address) <<SwapRouter02>> + + + diff --git a/examples/uniswap-router.svg b/examples/uniswap-router.svg new file mode 100644 index 00000000..c3715e58 --- /dev/null +++ b/examples/uniswap-router.svg @@ -0,0 +1,362 @@ + + + + + + +UmlClassDiagram + + + +59 + +<<Abstract>> +BlockTimestamp +@uniswap/v3-periphery/contracts/base/BlockTimestamp.sol + + + + + +74 + +<<Abstract>> +Multicall +@uniswap/v3-periphery/contracts/base/Multicall.sol + +Public: +    <<payable>> multicall(data: bytes[]): (results: bytes[]) + + + +2 + +<<Abstract>> +PeripheryImmutableState +@uniswap/v3-periphery/contracts/base/PeripheryImmutableState.sol + +Public: +   factory: address +   WETH9: address + +Public: +    constructor(_factory: address, _WETH9: address) + + + +36 + +<<Abstract>> +PeripheryPayments +@uniswap/v3-periphery/contracts/base/PeripheryPayments.sol + +External: +    <<payable>> null() +    <<payable>> refundETH() +Public: +    <<payable>> unwrapWETH9(amountMinimum: uint256, recipient: address) +    <<payable>> sweepToken(token: address, amountMinimum: uint256, recipient: address) + + + +36->2 + + + + + +33 + +<<Abstract>> +PeripheryPaymentsWithFee +@uniswap/v3-periphery/contracts/base/PeripheryPaymentsWithFee.sol + +Public: +    <<payable>> unwrapWETH9WithFee(amountMinimum: uint256, recipient: address, feeBips: uint256, feeRecipient: address) +    <<payable>> sweepTokenWithFee(token: address, amountMinimum: uint256, recipient: address, feeBips: uint256, feeRecipient: address) + + + +33->36 + + + + + +76 + +<<Abstract>> +PeripheryValidation +@uniswap/v3-periphery/contracts/base/PeripheryValidation.sol + +Public: +    <<modifier>> checkDeadline(deadline: uint256) + + + +76->59 + + + + + +1 + +<<Abstract>> +SelfPermit +@uniswap/v3-periphery/contracts/base/SelfPermit.sol + +External: +    <<payable>> selfPermitIfNecessary(token: address, value: uint256, deadline: uint256, v: uint8, r: bytes32, s: bytes32) +    <<payable>> selfPermitAllowedIfNecessary(token: address, nonce: uint256, expiry: uint256, v: uint8, r: bytes32, s: bytes32) +Public: +    <<payable>> selfPermit(token: address, value: uint256, deadline: uint256, v: uint8, r: bytes32, s: bytes32) +    <<payable>> selfPermitAllowed(token: address, nonce: uint256, expiry: uint256, v: uint8, r: bytes32, s: bytes32) + + + +0 + +SwapRouter02 +contracts/SwapRouter02.sol + +Public: +    constructor(_factoryV2: address, factoryV3: address, _positionManager: address, _WETH9: address) + + + +0->1 + + + + + +4 + +<<Abstract>> +V2SwapRouter +contracts/V2SwapRouter.sol + +External: +    <<payable>> swapExactTokensForTokens(amountIn: uint256, amountOutMin: uint256, path: address[], to: address): (amountOut: uint256) +    <<payable>> swapTokensForExactTokens(amountOut: uint256, amountInMax: uint256, path: address[], to: address): (amountIn: uint256) + + + +0->4 + + + + + +5 + +<<Abstract>> +V3SwapRouter +contracts/V3SwapRouter.sol + + + +External: +    <<payable>> exactInputSingle(params: ExactInputSingleParams): (amountOut: uint256) +    <<payable>> exactInput(params: ExactInputParams): (amountOut: uint256) +    <<payable>> exactOutputSingle(params: ExactOutputSingleParams): (amountIn: uint256) +    <<payable>> exactOutput(params: ExactOutputParams): (amountIn: uint256) +    uniswapV3SwapCallback(amount0Delta: int256, amount1Delta: int256, _data: bytes) + + + +0->5 + + + + + +7 + +<<Abstract>> +ApproveAndCall +contracts/base/ApproveAndCall.sol + +External: +    <<payable>> approveMax(token: address) +    <<payable>> approveMaxMinusOne(token: address) +    <<payable>> approveZeroThenMax(token: address) +    <<payable>> approveZeroThenMaxMinusOne(token: address) +    <<payable>> mint(params: MintParams): (result: bytes) +    <<payable>> increaseLiquidity(params: IncreaseLiquidityParams): (result: bytes) +    getApprovalType(token: address, amount: uint256): ApprovalType +Public: +    <<payable>> callPositionManager(data: bytes): (result: bytes) + + + +0->7 + + + + + +8 + +<<Abstract>> +MulticallExtended +contracts/base/MulticallExtended.sol + +External: +    <<payable>> multicall(deadline: uint256, data: bytes[]): bytes[] <<checkDeadline>> +    <<payable>> multicall(previousBlockhash: bytes32, data: bytes[]): bytes[] <<checkPreviousBlockhash>> + + + +0->8 + + + + + +28 + +<<Abstract>> +ImmutableState +contracts/base/ImmutableState.sol + +Public: +   factoryV2: address +   positionManager: address + +Public: +    constructor(_factoryV2: address, _positionManager: address) + + + +4->28 + + + + + +29 + +<<Abstract>> +PeripheryPaymentsWithFeeExtended +contracts/base/PeripheryPaymentsWithFeeExtended.sol + +External: +    <<payable>> unwrapWETH9WithFee(amountMinimum: uint256, feeBips: uint256, feeRecipient: address) +    <<payable>> sweepTokenWithFee(token: address, amountMinimum: uint256, feeBips: uint256, feeRecipient: address) + + + +4->29 + + + + + +50 + +<<Abstract>> +OracleSlippage +contracts/base/OracleSlippage.sol + +External: +    checkOracleSlippage(path: bytes, maximumTickDivergence: uint24, secondsAgo: uint32) +    checkOracleSlippage(paths: bytes[], amounts: uint128[], maximumTickDivergence: uint24, secondsAgo: uint32) + + + +5->50 + + + + + +5->29 + + + + + +7->28 + + + + + +8->74 + + + + + +75 + +<<Abstract>> +PeripheryValidationExtended +contracts/base/PeripheryValidationExtended.sol + +Public: +    <<modifier>> checkPreviousBlockhash(previousBlockhash: bytes32) + + + +8->75 + + + + + +50->59 + + + + + +50->2 + + + + + +35 + +<<Abstract>> +PeripheryPaymentsExtended +contracts/base/PeripheryPaymentsExtended.sol + +External: +    <<payable>> unwrapWETH9(amountMinimum: uint256) +    <<payable>> wrapETH(value: uint256) +    <<payable>> sweepToken(token: address, amountMinimum: uint256) +    <<payable>> pull(token: address, value: uint256) + + + +35->36 + + + + + +29->33 + + + + + +29->35 + + + + + +75->76 + + + + + diff --git a/lib/converterClass2Dot.d.ts b/lib/converterClass2Dot.d.ts index ed12da33..6574c136 100644 --- a/lib/converterClass2Dot.d.ts +++ b/lib/converterClass2Dot.d.ts @@ -12,5 +12,6 @@ export interface ClassOptions { hidePrivates?: boolean; hideAbstracts?: boolean; hideFilename?: boolean; + hideSourceContract?: boolean; } export declare const convertClass2Dot: (umlClass: UmlClass, options?: ClassOptions) => string; diff --git a/lib/converterClass2Dot.js b/lib/converterClass2Dot.js index b25b0868..dc2c1cef 100644 --- a/lib/converterClass2Dot.js +++ b/lib/converterClass2Dot.js @@ -71,7 +71,8 @@ const dotAttributeVisibilities = (umlClass, options) => { if (umlClass.stereotype === umlClass_1.ClassStereotype.Struct || umlClass.stereotype === umlClass_1.ClassStereotype.Enum || umlClass.stereotype === umlClass_1.ClassStereotype.Constant) { - return dotString + dotAttributes(umlClass.attributes, undefined, false); + return (dotString + + dotAttributes(umlClass.attributes, options, undefined, false)); } // For each visibility group for (const vizGroup of ['Private', 'Internal', 'External', 'Public']) { @@ -100,11 +101,11 @@ const dotAttributeVisibilities = (umlClass, options) => { attributes.push(attribute); } } - dotString += dotAttributes(attributes, vizGroup); + dotString += dotAttributes(attributes, options, vizGroup); } return dotString; }; -const dotAttributes = (attributes, vizGroup, indent = true) => { +const dotAttributes = (attributes, options, vizGroup, indent = true) => { if (!attributes || attributes.length === 0) { return ''; } @@ -112,7 +113,10 @@ const dotAttributes = (attributes, vizGroup, indent = true) => { let dotString = vizGroup ? vizGroup + ':\\l' : ''; // for each attribute attributes.forEach((attribute) => { - dotString += `${indentString}${attribute.name}: ${attribute.type}\\l`; + const sourceContract = attribute.sourceContract && !options.hideSourceContract + ? ` \\<\\<${attribute.sourceContract}\\>\\>` + : ''; + dotString += `${indentString}${attribute.name}: ${attribute.type}${sourceContract}\\l`; }); return dotString; }; @@ -179,6 +183,8 @@ const dotOperators = (umlClass, vizGroup, operators, options) => { if (options.hideModifiers === false && operator.modifiers?.length > 0) { dotString += ` \\<\\<${operator.modifiers.join(', ')}\\>\\>`; } + if (operator.sourceContract && !options.hideSourceContract) + dotString += ` \\<\\<${operator.sourceContract}\\>\\>`; dotString += '\\l'; } return dotString; diff --git a/lib/filterClasses.d.ts b/lib/filterClasses.d.ts index 82053863..47ad4d5b 100644 --- a/lib/filterClasses.d.ts +++ b/lib/filterClasses.d.ts @@ -1,5 +1,7 @@ import { WeightedDiGraph } from 'js-graph-algorithms'; import { UmlClass } from './umlClass'; +import { ClassOptions } from './converterClass2Dot'; +export declare const filterHiddenClasses: (umlClasses: UmlClass[], options: ClassOptions) => UmlClass[]; export declare const classesConnectedToBaseContracts: (umlClasses: UmlClass[], baseContractNames: string[], depth?: number) => UmlClass[]; export declare const classesConnectedToBaseContract: (umlClasses: UmlClass[], baseContractName: string, weightedDirectedGraph: WeightedDiGraph, depth?: number) => { [contractName: string]: UmlClass; diff --git a/lib/filterClasses.js b/lib/filterClasses.js index e8e37ae0..620f9bf4 100644 --- a/lib/filterClasses.js +++ b/lib/filterClasses.js @@ -1,8 +1,24 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.topologicalSortClasses = exports.classesConnectedToBaseContract = exports.classesConnectedToBaseContracts = void 0; +exports.topologicalSortClasses = exports.classesConnectedToBaseContract = exports.classesConnectedToBaseContracts = exports.filterHiddenClasses = void 0; const js_graph_algorithms_1 = require("js-graph-algorithms"); +const umlClass_1 = require("./umlClass"); const associations_1 = require("./associations"); +const filterHiddenClasses = (umlClasses, options) => { + return umlClasses.filter((u) => (u.stereotype === umlClass_1.ClassStereotype.Enum && !options.hideEnums) || + (u.stereotype === umlClass_1.ClassStereotype.Struct && !options.hideStructs) || + (u.stereotype === umlClass_1.ClassStereotype.Abstract && + !options.hideAbstracts) || + (u.stereotype === umlClass_1.ClassStereotype.Interface && + !options.hideInterfaces) || + (u.stereotype === umlClass_1.ClassStereotype.Constant && + !options.hideConstants) || + (u.stereotype === umlClass_1.ClassStereotype.Library && + !options.hideLibraries) || + u.stereotype === umlClass_1.ClassStereotype.None || + u.stereotype === umlClass_1.ClassStereotype.Contract); +}; +exports.filterHiddenClasses = filterHiddenClasses; const classesConnectedToBaseContracts = (umlClasses, baseContractNames, depth) => { let filteredUmlClasses = {}; const weightedDirectedGraph = loadWeightedDirectedGraph(umlClasses); @@ -35,7 +51,9 @@ const classesConnectedToBaseContract = (umlClasses, baseContractName, weightedDi }; exports.classesConnectedToBaseContract = classesConnectedToBaseContract; function loadWeightedDirectedGraph(umlClasses) { - const weightedDirectedGraph = new js_graph_algorithms_1.WeightedDiGraph(umlClasses.length); // the number vertices in the graph + const weightedDirectedGraph = new js_graph_algorithms_1.WeightedDiGraph( + // the number vertices in the graph + umlClass_1.UmlClass.idCounter + 1); for (const sourceUmlClass of umlClasses) { for (const association of Object.values(sourceUmlClass.associations)) { // Find the first UML Class that matches the target class name @@ -43,6 +61,8 @@ function loadWeightedDirectedGraph(umlClasses) { if (!targetUmlClass) { continue; } + const isTarget = umlClasses.find((u) => u.id === targetUmlClass.id); + console.log(`isTarget ${isTarget} Adding edge from ${sourceUmlClass.name} with id ${sourceUmlClass.id} to ${targetUmlClass.name} with id ${targetUmlClass.id} and type ${targetUmlClass.stereotype}`); weightedDirectedGraph.addEdge(new js_graph_algorithms_1.Edge(sourceUmlClass.id, targetUmlClass.id, 1)); } } diff --git a/lib/sol2uml.js b/lib/sol2uml.js index caf8da3e..61a57c2e 100755 --- a/lib/sol2uml.js +++ b/lib/sol2uml.js @@ -11,6 +11,7 @@ const converterStorage2Dot_1 = require("./converterStorage2Dot"); const regEx_1 = require("./utils/regEx"); const writerFiles_1 = require("./writerFiles"); const path_1 = require("path"); +const squashClasses_1 = require("./squashClasses"); const program = new commander_1.Command(); const version = (0, path_1.basename)(__dirname) === 'lib' ? require('../package.json').version // used when run from compile js in /lib @@ -69,6 +70,8 @@ If an Ethereum address with a 0x prefix is passed, the verified source code from .option('-hi, --hideInterfaces', 'hide interfaces', false) .option('-ha, --hideAbstracts', 'hide abstract contracts', false) .option('-hn, --hideFilename', 'hide relative path and file name', false) + .option('-s, --squash', 'squash inherited contracts to the base contract(s)', false) + .option('-hsc, --hideSourceContract', 'hide the source contract when using squash', false) .action(async (fileFolderAddress, options, command) => { try { const combinedOptions = { @@ -76,12 +79,23 @@ If an Ethereum address with a 0x prefix is passed, the verified source code from ...options, }; let { umlClasses, contractName } = await (0, parserGeneral_1.parserUmlClasses)(fileFolderAddress, combinedOptions); - let filteredUmlClasses = umlClasses; - if (options.baseContractNames) { - const baseContractNames = options.baseContractNames.split(','); - filteredUmlClasses = (0, filterClasses_1.classesConnectedToBaseContracts)(umlClasses, baseContractNames, options.depth); + if (options.squash && + // Must specify base contract(s) or parse from Etherscan to get contractName + !(options.baseContractNames || contractName)) { + throw Error('Must specify base contract(s) when using the squash option against local Solidity files.'); + } + // Filter out any class stereotypes that are to be hidden + let filteredUmlClasses = (0, filterClasses_1.filterHiddenClasses)(umlClasses, options); + const baseContractNames = options.baseContractNames?.split(','); + if (baseContractNames) { + // Find all the classes connected to the base classes + filteredUmlClasses = (0, filterClasses_1.classesConnectedToBaseContracts)(filteredUmlClasses, baseContractNames, options.depth); contractName = baseContractNames[0]; } + // squash contracts + if (options.squash) { + filteredUmlClasses = (0, squashClasses_1.squashUmlClasses)(filteredUmlClasses, baseContractNames || [contractName]); + } const dotString = (0, converterClasses2Dot_1.convertUmlClasses2Dot)(filteredUmlClasses, combinedOptions.clusterFolders, combinedOptions); await (0, writerFiles_1.writeOutputFiles)(dotString, fileFolderAddress, contractName || 'classDiagram', combinedOptions.outputFormat, combinedOptions.outputFileName); debug(`Finished generating UML`); diff --git a/lib/squashClasses.d.ts b/lib/squashClasses.d.ts new file mode 100644 index 00000000..4180ceae --- /dev/null +++ b/lib/squashClasses.d.ts @@ -0,0 +1,2 @@ +import { UmlClass } from './umlClass'; +export declare const squashUmlClasses: (umlClasses: UmlClass[], squashContractNames: string[]) => UmlClass[]; diff --git a/lib/squashClasses.js b/lib/squashClasses.js new file mode 100644 index 00000000..dffcf5e1 --- /dev/null +++ b/lib/squashClasses.js @@ -0,0 +1,143 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.squashUmlClasses = void 0; +const umlClass_1 = require("./umlClass"); +const crypto = __importStar(require("crypto")); +const debug = require('debug')('sol2uml'); +const squashUmlClasses = (umlClasses, squashContractNames) => { + let removedClassIds = []; + for (const squashContractName of squashContractNames) { + // Find the base UML Class to squash + let baseIndex = umlClasses.findIndex(({ name }) => { + return name === squashContractName; + }); + if (baseIndex === undefined) { + throw Error(`Failed to find contract with name "${squashContractName}" to squash`); + } + const baseClass = umlClasses[baseIndex]; + let squashedClass = new umlClass_1.UmlClass({ + name: baseClass.name, + absolutePath: baseClass.absolutePath, + relativePath: baseClass.relativePath, + }); + squashedClass.id = baseClass.id; + const result = recursiveSquash(squashedClass, [], baseClass, umlClasses, 1); + removedClassIds = removedClassIds.concat(result.removedClassIds); + // Remove overridden functions from squashed class + squashedClass.operators = reduceOperators(squashedClass.operators); + umlClasses[baseIndex] = squashedClass; + } + // filter the list of classes that will be rendered + return umlClasses.filter((u) => + // remove any squashed inherited contracts + !removedClassIds.includes(u.id) || + // Include all base contracts + squashContractNames.includes(u.name)); +}; +exports.squashUmlClasses = squashUmlClasses; +const recursiveSquash = (squashedClass, inheritedContractNames, baseClass, umlClasses, startPosition) => { + let currentPosition = startPosition; + const removedClassIds = []; + // For each association from the baseClass + for (const [targetClassName, association] of Object.entries(baseClass.associations)) { + // if inheritance and (Abstract or Contract) + // Libraries and Interfaces will be copied + if (association.realization) { + // Find the target UML Class + const inheritedContract = umlClasses.find(({ name }) => { + return name === targetClassName; + }); + if (!inheritedContract) { + debug(`Warning: failed to find inherited contract with name ${targetClassName}`); + continue; + } + // Is the associated class a contract or abstract contract? + if (inheritedContract?.stereotype === umlClass_1.ClassStereotype.Library) { + squashedClass.addAssociation(association); + } + else { + // has the contract already been added to the inheritance tree? + const alreadyInherited = inheritedContractNames.includes(inheritedContract.name); + // Do not add inherited contract if it has already been added to the inheritance tree + if (!alreadyInherited) { + inheritedContractNames.push(inheritedContract.name); + const squashResult = recursiveSquash(squashedClass, inheritedContractNames, inheritedContract, umlClasses, currentPosition++); + // Add to list of removed class ids + removedClassIds.push(...squashResult.removedClassIds, inheritedContract.id); + } + } + } + else { + // Copy association but will not duplicate it + squashedClass.addAssociation(association); + } + } + // Copy class properties from the baseClass to the squashedClass + baseClass.constants.forEach((c) => squashedClass.constants.push({ ...c, sourceContract: baseClass.name })); + baseClass.attributes.forEach((a) => squashedClass.attributes.push({ ...a, sourceContract: baseClass.name })); + baseClass.enums.forEach((e) => squashedClass.enums.push(e)); + baseClass.structs.forEach((s) => squashedClass.structs.push(s)); + baseClass.imports.forEach((i) => squashedClass.imports.push(i)); + // copy the functions + baseClass.operators.forEach((f) => squashedClass.operators.push({ + ...f, + hash: hash(f), + inheritancePosition: currentPosition, + sourceContract: baseClass.name, + })); + return { + currentPosition, + removedClassIds, + }; +}; +const hash = (operator) => { + const hash = crypto.createHash('sha256'); + let data = operator.name ?? 'fallback'; + operator.parameters?.forEach((p) => { + data += ',' + p.type; + }); + operator.returnParameters?.forEach((p) => { + data += ',' + p.type; + }); + return hash.update(data).digest('hex'); +}; +const reduceOperators = (operators) => { + const hashes = new Set(operators.map((o) => o.hash)); + const operatorsWithNoHash = operators.filter((o) => !o.hash); + const newOperators = []; + for (const hash of hashes) { + const operator = operators + .filter((o) => o.hash === hash) + // sort operators by inheritance position. smaller to highest + .sort((o) => o.inheritancePosition) + // get last operator in the array + .slice(-1)[0]; + newOperators.push(operator); + } + newOperators.push(...operatorsWithNoHash); + return newOperators; +}; +//# sourceMappingURL=squashClasses.js.map \ No newline at end of file diff --git a/lib/umlClass.d.ts b/lib/umlClass.d.ts index afc84193..8861772c 100644 --- a/lib/umlClass.d.ts +++ b/lib/umlClass.d.ts @@ -44,6 +44,7 @@ export interface Attribute { type?: string; attributeType?: AttributeType; compiled?: boolean; + sourceContract?: string; } export interface Parameter { name?: string; @@ -55,6 +56,9 @@ export interface Operator extends Attribute { returnParameters?: Parameter[]; isPayable?: boolean; modifiers?: string[]; + hash?: string; + inheritancePosition?: number; + sourceContract?: string; } export declare enum ReferenceType { Memory = 0, @@ -63,12 +67,12 @@ export declare enum ReferenceType { export interface Association { referenceType: ReferenceType; targetUmlClassName: string; - targetUmlClassStereotype?: ClassStereotype; realization?: boolean; } export interface Constants { name: string; value: number; + sourceContract?: string; } export interface ClassProperties { name: string; diff --git a/lib/umlClass.js b/lib/umlClass.js index f67346e9..56a7ef8b 100644 --- a/lib/umlClass.js +++ b/lib/umlClass.js @@ -45,6 +45,7 @@ var ReferenceType; })(ReferenceType = exports.ReferenceType || (exports.ReferenceType = {})); class UmlClass { constructor(properties) { + this.imports = []; this.constants = []; this.attributes = []; this.operators = []; @@ -83,9 +84,7 @@ class UmlClass { * Does not include any grand parent associations. That has to be done recursively. */ getParentContracts() { - return Object.values(this.associations).filter((association) => association.realization && - association.targetUmlClassStereotype !== - ClassStereotype.Interface); + return Object.values(this.associations).filter((association) => association.realization); } } exports.UmlClass = UmlClass; diff --git a/package.json b/package.json index f1ea822e..9a08e94b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sol2uml", - "version": "2.2.6", + "version": "2.3.0", "description": "Solidity contract visualisation tool.", "main": "./lib/index.js", "types": "./lib/index.d.ts", diff --git a/src/contracts/squash.sol b/src/contracts/squash.sol new file mode 100644 index 00000000..67bfb3be --- /dev/null +++ b/src/contracts/squash.sol @@ -0,0 +1,77 @@ +pragma solidity ^0.8.16; + +contract CommonContract { + function commonFunction() public {} + function commonOverride() public virtual returns (address) {} +} + +contract GrandParentLeft { + uint256 integer1 = 1; + uint256 constant ConstInteger1 = 1; + + function basePublicFunctionNoParams() public virtual {} + function parentPublicFunctionNoParams() public virtual {} + function grandParentPublicFunctionNoParams() public virtual {} + + function basePublicFunctionIntParam(uint256 value) public virtual {} + + function overrideFunction(uint256 value) public virtual {} + function overrideGrantParentFunctionReturn(uint256 value, address account) public virtual returns (bool) {} + + function grandParentPrivateFunction() private {} + function grandParentInternalFunction() internal {} +} + +contract ParentLeft is GrandParentLeft, CommonContract { + uint256 integer2 = 2; + uint256 constant ConstInteger2 = 2; + + function basePublicFunctionNoParams() public virtual override {} + function parentPublicFunctionNoParams() public virtual override {} + + function basePublicFunctionIntParam(uint256 value) public virtual override {} + + function overrideFunction(uint256 value, address account) public virtual {} + function overrideParentFunctionReturn(uint256 value, address account) public virtual returns (bool, bool) {} + + function parentPrivateFunction() private {} + + function callInternalGrandParent() external { + grandParentInternalFunction(); + } +} + +contract GrandParentRight is CommonContract { + uint256 integer4 = 3; + uint256 constant ConstInteger4 = 3; + + function commonOverride() public virtual override returns (address) {} +} + +contract ParentRight is GrandParentRight { + uint256 integer5 = 4; + uint256 constant ConstInteger5 = 4; +} + +contract Squash is ParentLeft, ParentRight { + + bool baseBool = true; + bool constant BaseConstantBool = true; + + uint256 baseInteger = 5; + uint256 integer3 = 5; + uint256 integer6 = 5; + + uint256 constant ConstInteger3 = 5; + uint256 constant ConstInteger6 = 5; + + function commonOverride() public override (CommonContract, GrandParentRight) returns (address) {} + + function basePublicFunctionNoParams() public override {} + function basePublicFunctionIntParam(uint256 value) public virtual override {} + + function overrideFunction(uint256 value, address account) public override {} + function overrideFunction(uint256 value, address account, bool flag) public {} + + function basePrivateFunction() private {} +} diff --git a/src/ts/__tests__/fileParser.test.ts b/src/ts/__tests__/fileParser.test.ts index 8a944753..db5e95fb 100644 --- a/src/ts/__tests__/fileParser.test.ts +++ b/src/ts/__tests__/fileParser.test.ts @@ -6,7 +6,7 @@ describe('Parser', () => { const files = await getSolidityFilesFromFolderOrFile( './src/contracts' ) - expect(files).toHaveLength(27) + expect(files).toHaveLength(28) }) test('get Solidity files from folder with no sol files', async () => { diff --git a/src/ts/converterClass2Dot.ts b/src/ts/converterClass2Dot.ts index 816b08b7..c2fff9fd 100644 --- a/src/ts/converterClass2Dot.ts +++ b/src/ts/converterClass2Dot.ts @@ -23,6 +23,7 @@ export interface ClassOptions { hidePrivates?: boolean hideAbstracts?: boolean hideFilename?: boolean + hideSourceContract?: boolean } export const convertClass2Dot = ( @@ -105,7 +106,7 @@ const dotClassTitle = ( const dotAttributeVisibilities = ( umlClass: UmlClass, - options: { hidePrivates?: boolean } + options: { hidePrivates?: boolean; hideSourceContract?: boolean } ): string => { if (umlClass.attributes.length === 0) return '' @@ -116,7 +117,10 @@ const dotAttributeVisibilities = ( umlClass.stereotype === ClassStereotype.Enum || umlClass.stereotype === ClassStereotype.Constant ) { - return dotString + dotAttributes(umlClass.attributes, undefined, false) + return ( + dotString + + dotAttributes(umlClass.attributes, options, undefined, false) + ) } // For each visibility group @@ -154,7 +158,7 @@ const dotAttributeVisibilities = ( } } - dotString += dotAttributes(attributes, vizGroup) + dotString += dotAttributes(attributes, options, vizGroup) } return dotString @@ -162,6 +166,7 @@ const dotAttributeVisibilities = ( const dotAttributes = ( attributes: Attribute[], + options: { hideSourceContract?: boolean }, vizGroup?: string, indent = true ): string => { @@ -174,7 +179,11 @@ const dotAttributes = ( // for each attribute attributes.forEach((attribute) => { - dotString += `${indentString}${attribute.name}: ${attribute.type}\\l` + const sourceContract = + attribute.sourceContract && !options.hideSourceContract + ? ` \\<\\<${attribute.sourceContract}\\>\\>` + : '' + dotString += `${indentString}${attribute.name}: ${attribute.type}${sourceContract}\\l` }) return dotString @@ -240,6 +249,7 @@ const dotOperators = ( options: { hideModifiers?: boolean hideEvents?: boolean + hideSourceContract?: boolean } ): string => { // Skip if there are no operators @@ -283,6 +293,9 @@ const dotOperators = ( dotString += ` \\<\\<${operator.modifiers.join(', ')}\\>\\>` } + if (operator.sourceContract && !options.hideSourceContract) + dotString += ` \\<\\<${operator.sourceContract}\\>\\>` + dotString += '\\l' } diff --git a/src/ts/filterClasses.ts b/src/ts/filterClasses.ts index 03170ae5..2456b310 100644 --- a/src/ts/filterClasses.ts +++ b/src/ts/filterClasses.ts @@ -5,8 +5,30 @@ import { TopologicalSort, WeightedDiGraph, } from 'js-graph-algorithms' -import { UmlClass } from './umlClass' +import { ClassStereotype, UmlClass } from './umlClass' import { findAssociatedClass } from './associations' +import { ClassOptions } from './converterClass2Dot' + +export const filterHiddenClasses = ( + umlClasses: UmlClass[], + options: ClassOptions +): UmlClass[] => { + return umlClasses.filter( + (u) => + (u.stereotype === ClassStereotype.Enum && !options.hideEnums) || + (u.stereotype === ClassStereotype.Struct && !options.hideStructs) || + (u.stereotype === ClassStereotype.Abstract && + !options.hideAbstracts) || + (u.stereotype === ClassStereotype.Interface && + !options.hideInterfaces) || + (u.stereotype === ClassStereotype.Constant && + !options.hideConstants) || + (u.stereotype === ClassStereotype.Library && + !options.hideLibraries) || + u.stereotype === ClassStereotype.None || + u.stereotype === ClassStereotype.Contract + ) +} export const classesConnectedToBaseContracts = ( umlClasses: UmlClass[], @@ -63,7 +85,10 @@ export const classesConnectedToBaseContract = ( } function loadWeightedDirectedGraph(umlClasses: UmlClass[]): WeightedDiGraph { - const weightedDirectedGraph = new WeightedDiGraph(umlClasses.length) // the number vertices in the graph + const weightedDirectedGraph = new WeightedDiGraph( + // the number vertices in the graph + UmlClass.idCounter + 1 + ) for (const sourceUmlClass of umlClasses) { for (const association of Object.values(sourceUmlClass.associations)) { @@ -77,7 +102,10 @@ function loadWeightedDirectedGraph(umlClasses: UmlClass[]): WeightedDiGraph { if (!targetUmlClass) { continue } - + const isTarget = umlClasses.find((u) => u.id === targetUmlClass.id) + console.log( + `isTarget ${isTarget} Adding edge from ${sourceUmlClass.name} with id ${sourceUmlClass.id} to ${targetUmlClass.name} with id ${targetUmlClass.id} and type ${targetUmlClass.stereotype}` + ) weightedDirectedGraph.addEdge( new Edge(sourceUmlClass.id, targetUmlClass.id, 1) ) diff --git a/src/ts/sol2uml.ts b/src/ts/sol2uml.ts index 2dc6b2d1..40cfb7cf 100644 --- a/src/ts/sol2uml.ts +++ b/src/ts/sol2uml.ts @@ -3,7 +3,10 @@ import { convertUmlClasses2Dot } from './converterClasses2Dot' import { parserUmlClasses } from './parserGeneral' import { EtherscanParser, networks } from './parserEtherscan' -import { classesConnectedToBaseContracts } from './filterClasses' +import { + classesConnectedToBaseContracts, + filterHiddenClasses, +} from './filterClasses' import { Command, Option } from 'commander' import { addStorageValues, @@ -13,6 +16,7 @@ import { convertStorages2Dot } from './converterStorage2Dot' import { isAddress } from './utils/regEx' import { writeOutputFiles, writeSolidity } from './writerFiles' import { basename } from 'path' +import { squashUmlClasses } from './squashClasses' const program = new Command() const version = @@ -133,6 +137,16 @@ If an Ethereum address with a 0x prefix is passed, the verified source code from .option('-hi, --hideInterfaces', 'hide interfaces', false) .option('-ha, --hideAbstracts', 'hide abstract contracts', false) .option('-hn, --hideFilename', 'hide relative path and file name', false) + .option( + '-s, --squash', + 'squash inherited contracts to the base contract(s)', + false + ) + .option( + '-hsc, --hideSourceContract', + 'hide the source contract when using squash', + false + ) .action(async (fileFolderAddress, options, command) => { try { const combinedOptions = { @@ -145,17 +159,38 @@ If an Ethereum address with a 0x prefix is passed, the verified source code from combinedOptions ) - let filteredUmlClasses = umlClasses - if (options.baseContractNames) { - const baseContractNames = options.baseContractNames.split(',') + if ( + options.squash && + // Must specify base contract(s) or parse from Etherscan to get contractName + !(options.baseContractNames || contractName) + ) { + throw Error( + 'Must specify base contract(s) when using the squash option against local Solidity files.' + ) + } + + // Filter out any class stereotypes that are to be hidden + let filteredUmlClasses = filterHiddenClasses(umlClasses, options) + + const baseContractNames = options.baseContractNames?.split(',') + if (baseContractNames) { + // Find all the classes connected to the base classes filteredUmlClasses = classesConnectedToBaseContracts( - umlClasses, + filteredUmlClasses, baseContractNames, options.depth ) contractName = baseContractNames[0] } + // squash contracts + if (options.squash) { + filteredUmlClasses = squashUmlClasses( + filteredUmlClasses, + baseContractNames || [contractName] + ) + } + const dotString = convertUmlClasses2Dot( filteredUmlClasses, combinedOptions.clusterFolders, diff --git a/src/ts/squashClasses.ts b/src/ts/squashClasses.ts new file mode 100644 index 00000000..6fdad5cf --- /dev/null +++ b/src/ts/squashClasses.ts @@ -0,0 +1,173 @@ +import { ClassStereotype, Operator, UmlClass } from './umlClass' +import * as crypto from 'crypto' + +const debug = require('debug')('sol2uml') + +export const squashUmlClasses = ( + umlClasses: UmlClass[], + squashContractNames: string[] +): UmlClass[] => { + let removedClassIds: number[] = [] + for (const squashContractName of squashContractNames) { + // Find the base UML Class to squash + let baseIndex = umlClasses.findIndex(({ name }) => { + return name === squashContractName + }) + if (baseIndex === undefined) { + throw Error( + `Failed to find contract with name "${squashContractName}" to squash` + ) + } + const baseClass = umlClasses[baseIndex] + + let squashedClass = new UmlClass({ + name: baseClass.name, + absolutePath: baseClass.absolutePath, + relativePath: baseClass.relativePath, + }) + squashedClass.id = baseClass.id + const result = recursiveSquash( + squashedClass, + [], + baseClass, + umlClasses, + 1 + ) + removedClassIds = removedClassIds.concat(result.removedClassIds) + + // Remove overridden functions from squashed class + squashedClass.operators = reduceOperators(squashedClass.operators) + + umlClasses[baseIndex] = squashedClass + } + + // filter the list of classes that will be rendered + return umlClasses.filter( + (u) => + // remove any squashed inherited contracts + !removedClassIds.includes(u.id) || + // Include all base contracts + squashContractNames.includes(u.name) + ) +} + +const recursiveSquash = ( + squashedClass: UmlClass, + inheritedContractNames: string[], + baseClass: UmlClass, + umlClasses: UmlClass[], + startPosition: number +): { currentPosition: number; removedClassIds: number[] } => { + let currentPosition = startPosition + const removedClassIds: number[] = [] + + // For each association from the baseClass + for (const [targetClassName, association] of Object.entries( + baseClass.associations + )) { + // if inheritance and (Abstract or Contract) + // Libraries and Interfaces will be copied + if (association.realization) { + // Find the target UML Class + const inheritedContract = umlClasses.find(({ name }) => { + return name === targetClassName + }) + if (!inheritedContract) { + debug( + `Warning: failed to find inherited contract with name ${targetClassName}` + ) + continue + } + + // Is the associated class a contract or abstract contract? + if (inheritedContract?.stereotype === ClassStereotype.Library) { + squashedClass.addAssociation(association) + } else { + // has the contract already been added to the inheritance tree? + const alreadyInherited = inheritedContractNames.includes( + inheritedContract.name + ) + // Do not add inherited contract if it has already been added to the inheritance tree + if (!alreadyInherited) { + inheritedContractNames.push(inheritedContract.name) + const squashResult = recursiveSquash( + squashedClass, + inheritedContractNames, + inheritedContract, + umlClasses, + currentPosition++ + ) + // Add to list of removed class ids + removedClassIds.push( + ...squashResult.removedClassIds, + inheritedContract.id + ) + } + } + } else { + // Copy association but will not duplicate it + squashedClass.addAssociation(association) + } + } + + // Copy class properties from the baseClass to the squashedClass + baseClass.constants.forEach((c) => + squashedClass.constants.push({ ...c, sourceContract: baseClass.name }) + ) + baseClass.attributes.forEach((a) => + squashedClass.attributes.push({ ...a, sourceContract: baseClass.name }) + ) + baseClass.enums.forEach((e) => squashedClass.enums.push(e)) + baseClass.structs.forEach((s) => squashedClass.structs.push(s)) + + baseClass.imports.forEach((i) => squashedClass.imports.push(i)) + + // copy the functions + baseClass.operators.forEach((f) => + squashedClass.operators.push({ + ...f, + hash: hash(f), + inheritancePosition: currentPosition, + sourceContract: baseClass.name, + }) + ) + + return { + currentPosition, + removedClassIds, + } +} + +const hash = (operator: Operator): string => { + const hash = crypto.createHash('sha256') + + let data = operator.name ?? 'fallback' + operator.parameters?.forEach((p) => { + data += ',' + p.type + }) + operator.returnParameters?.forEach((p) => { + data += ',' + p.type + }) + + return hash.update(data).digest('hex') +} + +const reduceOperators = (operators: Operator[]): Operator[] => { + const hashes = new Set(operators.map((o) => o.hash)) + + const operatorsWithNoHash = operators.filter((o) => !o.hash) + + const newOperators: Operator[] = [] + for (const hash of hashes) { + const operator = operators + .filter((o) => o.hash === hash) + // sort operators by inheritance position. smaller to highest + .sort((o) => o.inheritancePosition) + // get last operator in the array + .slice(-1)[0] + newOperators.push(operator) + } + newOperators.push(...operatorsWithNoHash) + + return newOperators +} diff --git a/src/ts/umlClass.ts b/src/ts/umlClass.ts index 9b61a750..e6b04bd3 100644 --- a/src/ts/umlClass.ts +++ b/src/ts/umlClass.ts @@ -50,6 +50,8 @@ export interface Attribute { type?: string attributeType?: AttributeType compiled?: boolean // true for constants and immutables + // Used for squashed classes + sourceContract?: string } export interface Parameter { @@ -64,6 +66,10 @@ export interface Operator extends Attribute { returnParameters?: Parameter[] isPayable?: boolean modifiers?: string[] + // Used by squashed classes + hash?: string + inheritancePosition?: number + sourceContract?: string } export enum ReferenceType { @@ -74,13 +80,14 @@ export enum ReferenceType { export interface Association { referenceType: ReferenceType targetUmlClassName: string - targetUmlClassStereotype?: ClassStereotype realization?: boolean } export interface Constants { name: string value: number + // Used for squashed classes + sourceContract?: string } export interface ClassProperties { @@ -104,7 +111,7 @@ export class UmlClass implements ClassProperties { name: string absolutePath: string relativePath: string - imports: Import[] + imports: Import[] = [] stereotype?: ClassStereotype constants: Constants[] = [] @@ -159,10 +166,7 @@ export class UmlClass implements ClassProperties { */ getParentContracts(): Association[] { return Object.values(this.associations).filter( - (association) => - association.realization && - association.targetUmlClassStereotype !== - ClassStereotype.Interface + (association) => association.realization ) } }