From 5fe3425b23b3c0c6e21cbab42b25f7832fcfde54 Mon Sep 17 00:00:00 2001 From: Daniel Beal Date: Thu, 29 Jun 2023 12:50:05 -0700 Subject: [PATCH] fix critical issues * use boolean to represent if migration is in progress * implement onERC721Received * add tests to verify resolution of the reported security issues --- .../legacy-market/contracts/LegacyMarket.sol | 42 +- .../test/integration/LegacyMarket.ts | 71 +++- markets/perps-market/storage.dump.sol | 398 +++--------------- 3 files changed, 176 insertions(+), 335 deletions(-) diff --git a/markets/legacy-market/contracts/LegacyMarket.sol b/markets/legacy-market/contracts/LegacyMarket.sol index faf9689fe6..1093c03806 100644 --- a/markets/legacy-market/contracts/LegacyMarket.sol +++ b/markets/legacy-market/contracts/LegacyMarket.sol @@ -1,6 +1,7 @@ //SPDX-License-Identifier: MIT pragma solidity >=0.8.11 <0.9.0; +import {SafeCastU256} from "@synthetixio/core-contracts/contracts/utils/SafeCast.sol"; import "@synthetixio/main/contracts/interfaces/external/IMarket.sol"; import "./interfaces/external/ILiquidatorRewards.sol"; import "./interfaces/external/IIssuer.sol"; @@ -16,11 +17,13 @@ import "./interfaces/external/IRewardEscrowV2.sol"; import "@synthetixio/core-contracts/contracts/ownership/Ownable.sol"; import "@synthetixio/core-contracts/contracts/interfaces/IERC20.sol"; import "@synthetixio/core-contracts/contracts/interfaces/IERC721.sol"; +import "@synthetixio/core-contracts/contracts/interfaces/IERC721Receiver.sol"; import "@synthetixio/core-contracts/contracts/utils/DecimalMath.sol"; import "@synthetixio/core-contracts/contracts/errors/ParameterError.sol"; -contract LegacyMarket is ILegacyMarket, Ownable, UUPSImplementation, IMarket { +contract LegacyMarket is ILegacyMarket, Ownable, UUPSImplementation, IMarket, IERC721Receiver { + using SafeCastU256 for uint256; using DecimalMath for uint256; uint128 public marketId; @@ -28,11 +31,14 @@ contract LegacyMarket is ILegacyMarket, Ownable, UUPSImplementation, IMarket { bool public pauseMigration; // used by _migrate to temporarily set reportedDebt to another value before - uint256 tmpLockedDebt; + uint128 tmpLockedDebt; + + bool migrationInProgress; IAddressResolver public v2xResolver; IV3CoreProxy public v3System; + error MigrationInProgress(); error MarketAlreadyRegistered(uint256 existingMarketId); error NothingToMigrate(); error InsufficientCollateralMigrated(uint256 amountRequested, uint256 amountAvailable); @@ -77,7 +83,7 @@ contract LegacyMarket is ILegacyMarket, Ownable, UUPSImplementation, IMarket { if (marketId == requestedMarketId) { // in cases where we are in the middle of an account migration, we want to prevent the debt from changing, so we "lock" the value to the amount as the call starts // so we can detect the increase and associate it properly later. - if (tmpLockedDebt != 0) { + if (migrationInProgress) { return tmpLockedDebt; } @@ -166,11 +172,17 @@ contract LegacyMarket is ILegacyMarket, Ownable, UUPSImplementation, IMarket { * @dev Migrates {staker} from V2 to {accountId} in V3. */ function _migrate(address staker, uint128 accountId) internal { - // start building the staker's v3 account - v3System.createAccount(accountId); + // sanity + if (migrationInProgress) { + revert MigrationInProgress(); + } // find out how much debt is on the v2x system - tmpLockedDebt = reportedDebt(marketId); + tmpLockedDebt = reportedDebt(marketId).to128(); + migrationInProgress = true; + + // start building the staker's v3 account + v3System.createAccount(accountId); // get the address of the synthetix v2x proxy contract so we can manipulate the debt ISynthetix oldSynthetix = ISynthetix(v2xResolver.getAddress("ProxySynthetix")); @@ -216,6 +228,7 @@ contract LegacyMarket is ILegacyMarket, Ownable, UUPSImplementation, IMarket { // unlock the debt. now it will suddenly appear in subsequent call for association tmpLockedDebt = 0; + migrationInProgress = false; // now we can associate the debt to a single staker v3System.associateDebt( @@ -320,4 +333,21 @@ contract LegacyMarket is ILegacyMarket, Ownable, UUPSImplementation, IMarket { function upgradeTo(address to) external onlyOwner { _upgradeTo(to); } + + function onERC721Received( + address operator, + address /*from*/, + uint256 /*tokenId*/, + bytes memory /*data*/ + ) external view override returns (bytes4) { + if (operator != address(v3System)) { + revert ParameterError.InvalidParameter("operator", "should be account token"); + } + + if (!migrationInProgress) { + revert ParameterError.InvalidParameter("tokenId", "must be migrating account token"); + } + + return IERC721Receiver.onERC721Received.selector; + } } diff --git a/markets/legacy-market/test/integration/LegacyMarket.ts b/markets/legacy-market/test/integration/LegacyMarket.ts index 45b08383a3..a0494ed96c 100644 --- a/markets/legacy-market/test/integration/LegacyMarket.ts +++ b/markets/legacy-market/test/integration/LegacyMarket.ts @@ -152,7 +152,7 @@ describe('LegacyMarket', () => { ).toString() ); assertBn.gte( - await v3System.getWithdrawableUsd(await market.marketId()), + await v3System.getWithdrawableMarketUsd(await market.marketId()), convertedAmount.toBN() ); }); @@ -334,6 +334,62 @@ describe('LegacyMarket', () => { return market.connect(snxStaker).migrate(migratedAccountId); }); + describe('when we have some collateral already deposited from outside the v3 market', () => { + const accountId = 100; + const delegateAmount = ethers.utils.parseEther('100'); + + before(restore); + + before('delegate collateral outside of market', async () => { + // create user account + await v3System.connect(owner)['createAccount(uint128)'](100); + + // approve + await snxToken.connect(owner).approve(v3System.address, ethers.constants.MaxUint256); + + // stake collateral + await v3System.connect(owner).deposit(accountId, snxToken.address, delegateAmount); + + // invest in the pool + await v3System + .connect(owner) + .delegateCollateral( + accountId, + await v3System.getPreferredPool(), + snxToken.address, + delegateAmount, + ethers.utils.parseEther('1') + ); + + // sanity + assertBn.equal( + await v3System.callStatic.getPositionDebt( + accountId, + await v3System.getPreferredPool(), + snxToken.address + ), + 0 + ); + }); + + testMigrate(async () => { + return market.connect(snxStaker).migrate(migratedAccountId); + }); + + describe('post check', async () => { + it('should have no debt', async () => { + assertBn.equal( + await v3System.callStatic.getPositionDebt( + accountId, + await v3System.getPreferredPool(), + snxToken.address + ), + 0 + ); + }); + }); + }); + // the below tests are fork only if (hre.network.name === 'cannon') { describe('when all accounts migrate to v3', function () { @@ -369,4 +425,17 @@ describe('LegacyMarket', () => { it('sets the value', async () => {}); }); + + describe('onERC721Received()', async () => { + before(restore); + it('fails if you just send an account token nft to the market without migrating', async () => { + const func = 'safeTransferFrom(address,address,uint256)'; + await v3System.connect(owner)['createAccount(uint128)'](12341234); + await assertRevert( + v3Account.connect(owner)[func](await owner.getAddress(), market.address, 12341234), + 'InvalidTransferRecipient("0xD931006FdC3ba62E74Ae181CCBb1eA243AbE8956")', + market + ); + }); + }); }); diff --git a/markets/perps-market/storage.dump.sol b/markets/perps-market/storage.dump.sol index 02a13f1596..c1f058b337 100644 --- a/markets/perps-market/storage.dump.sol +++ b/markets/perps-market/storage.dump.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity >=0.8.11<0.9.0; +pragma solidity >=0.4.22<0.9.0; // @custom:artifact @synthetixio/core-contracts/contracts/ownership/OwnableStorage.sol:OwnableStorage library OwnableStorage { @@ -43,20 +43,6 @@ library DecimalMath { uint256 public constant PRECISION_FACTOR = 9; } -// @custom:artifact @synthetixio/core-contracts/contracts/utils/HeapUtil.sol:HeapUtil -library HeapUtil { - uint private constant _ROOT_INDEX = 1; - struct Data { - uint128 idCount; - Node[] nodes; - mapping(uint128 => uint) indices; - } - struct Node { - uint128 id; - int128 priority; - } -} - // @custom:artifact @synthetixio/core-contracts/contracts/utils/SetUtil.sol:SetUtil library SetUtil { struct UintSet { @@ -106,269 +92,6 @@ library FeatureFlag { } } -// @custom:artifact @synthetixio/main/contracts/storage/Account.sol:Account -library Account { - struct Data { - uint128 id; - AccountRBAC.Data rbac; - uint64 lastInteraction; - uint64 __slotAvailableForFutureUse; - uint128 __slot2AvailableForFutureUse; - mapping(address => Collateral.Data) collaterals; - } - function load(uint128 id) internal pure returns (Data storage account) { - bytes32 s = keccak256(abi.encode("io.synthetix.synthetix.Account", id)); - assembly { - account.slot := s - } - } -} - -// @custom:artifact @synthetixio/main/contracts/storage/AccountRBAC.sol:AccountRBAC -library AccountRBAC { - bytes32 internal constant _ADMIN_PERMISSION = "ADMIN"; - bytes32 internal constant _WITHDRAW_PERMISSION = "WITHDRAW"; - bytes32 internal constant _DELEGATE_PERMISSION = "DELEGATE"; - bytes32 internal constant _MINT_PERMISSION = "MINT"; - bytes32 internal constant _REWARDS_PERMISSION = "REWARDS"; - bytes32 internal constant _PERPS_MODIFY_COLLATERAL_PERMISSION = "PERPS_MODIFY_COLLATERAL"; - bytes32 internal constant _PERPS_COMMIT_ASYNC_ORDER_PERMISSION = "PERPS_COMMIT_ASYNC_ORDER"; - struct Data { - address owner; - mapping(address => SetUtil.Bytes32Set) permissions; - SetUtil.AddressSet permissionAddresses; - } -} - -// @custom:artifact @synthetixio/main/contracts/storage/Collateral.sol:Collateral -library Collateral { - struct Data { - uint256 amountAvailableForDelegationD18; - SetUtil.UintSet pools; - CollateralLock.Data[] locks; - } -} - -// @custom:artifact @synthetixio/main/contracts/storage/CollateralConfiguration.sol:CollateralConfiguration -library CollateralConfiguration { - bytes32 private constant _SLOT_AVAILABLE_COLLATERALS = keccak256(abi.encode("io.synthetix.synthetix.CollateralConfiguration_availableCollaterals")); - struct Data { - bool depositingEnabled; - uint256 issuanceRatioD18; - uint256 liquidationRatioD18; - uint256 liquidationRewardD18; - bytes32 oracleNodeId; - address tokenAddress; - uint256 minDelegationD18; - } - function load(address token) internal pure returns (Data storage collateralConfiguration) { - bytes32 s = keccak256(abi.encode("io.synthetix.synthetix.CollateralConfiguration", token)); - assembly { - collateralConfiguration.slot := s - } - } - function loadAvailableCollaterals() internal pure returns (SetUtil.AddressSet storage availableCollaterals) { - bytes32 s = _SLOT_AVAILABLE_COLLATERALS; - assembly { - availableCollaterals.slot := s - } - } -} - -// @custom:artifact @synthetixio/main/contracts/storage/CollateralLock.sol:CollateralLock -library CollateralLock { - struct Data { - uint128 amountD18; - uint64 lockExpirationTime; - } -} - -// @custom:artifact @synthetixio/main/contracts/storage/Config.sol:Config -library Config { - struct Data { - uint256 __unused; - } -} - -// @custom:artifact @synthetixio/main/contracts/storage/Distribution.sol:Distribution -library Distribution { - struct Data { - uint128 totalSharesD18; - int128 valuePerShareD27; - mapping(bytes32 => DistributionActor.Data) actorInfo; - } -} - -// @custom:artifact @synthetixio/main/contracts/storage/DistributionActor.sol:DistributionActor -library DistributionActor { - struct Data { - uint128 sharesD18; - int128 lastValuePerShareD27; - } -} - -// @custom:artifact @synthetixio/main/contracts/storage/Market.sol:Market -library Market { - struct Data { - uint128 id; - address marketAddress; - int128 netIssuanceD18; - int128 creditCapacityD18; - int128 lastDistributedMarketBalanceD18; - HeapUtil.Data inRangePools; - HeapUtil.Data outRangePools; - Distribution.Data poolsDebtDistribution; - mapping(uint128 => MarketPoolInfo.Data) pools; - DepositedCollateral[] depositedCollateral; - mapping(address => uint256) maximumDepositableD18; - uint32 minDelegateTime; - uint32 __reservedForLater1; - uint64 __reservedForLater2; - uint64 __reservedForLater3; - uint64 __reservedForLater4; - uint256 minLiquidityRatioD18; - } - struct DepositedCollateral { - address collateralType; - uint256 amountD18; - } - function load(uint128 id) internal pure returns (Data storage market) { - bytes32 s = keccak256(abi.encode("io.synthetix.synthetix.Market", id)); - assembly { - market.slot := s - } - } -} - -// @custom:artifact @synthetixio/main/contracts/storage/MarketConfiguration.sol:MarketConfiguration -library MarketConfiguration { - struct Data { - uint128 marketId; - uint128 weightD18; - int128 maxDebtShareValueD18; - } -} - -// @custom:artifact @synthetixio/main/contracts/storage/MarketPoolInfo.sol:MarketPoolInfo -library MarketPoolInfo { - struct Data { - uint128 creditCapacityAmountD18; - uint128 pendingDebtD18; - } -} - -// @custom:artifact @synthetixio/main/contracts/storage/OracleManager.sol:OracleManager -library OracleManager { - bytes32 private constant _SLOT_ORACLE_MANAGER = keccak256(abi.encode("io.synthetix.synthetix.OracleManager")); - struct Data { - address oracleManagerAddress; - } - function load() internal pure returns (Data storage oracleManager) { - bytes32 s = _SLOT_ORACLE_MANAGER; - assembly { - oracleManager.slot := s - } - } -} - -// @custom:artifact @synthetixio/main/contracts/storage/Pool.sol:Pool -library Pool { - bytes32 private constant _CONFIG_SET_MARKET_MIN_DELEGATE_MAX = "setMarketMinDelegateTime_max"; - struct Data { - uint128 id; - string name; - address owner; - address nominatedOwner; - uint128 totalWeightsD18; - int128 totalVaultDebtsD18; - MarketConfiguration.Data[] marketConfigurations; - Distribution.Data vaultsDebtDistribution; - mapping(address => Vault.Data) vaults; - uint64 lastConfigurationTime; - uint64 __reserved1; - uint64 __reserved2; - uint64 __reserved3; - } - function load(uint128 id) internal pure returns (Data storage pool) { - bytes32 s = keccak256(abi.encode("io.synthetix.synthetix.Pool", id)); - assembly { - pool.slot := s - } - } -} - -// @custom:artifact @synthetixio/main/contracts/storage/RewardDistribution.sol:RewardDistribution -library RewardDistribution { - struct Data { - address distributor; - uint128 __slotAvailableForFutureUse; - uint128 rewardPerShareD18; - mapping(uint256 => RewardDistributionClaimStatus.Data) claimStatus; - int128 scheduledValueD18; - uint64 start; - uint32 duration; - uint32 lastUpdate; - } -} - -// @custom:artifact @synthetixio/main/contracts/storage/RewardDistributionClaimStatus.sol:RewardDistributionClaimStatus -library RewardDistributionClaimStatus { - struct Data { - uint128 lastRewardPerShareD18; - uint128 pendingSendD18; - } -} - -// @custom:artifact @synthetixio/main/contracts/storage/ScalableMapping.sol:ScalableMapping -library ScalableMapping { - struct Data { - uint128 totalSharesD18; - int128 scaleModifierD27; - mapping(bytes32 => uint256) sharesD18; - } -} - -// @custom:artifact @synthetixio/main/contracts/storage/SystemPoolConfiguration.sol:SystemPoolConfiguration -library SystemPoolConfiguration { - bytes32 private constant _SLOT_SYSTEM_POOL_CONFIGURATION = keccak256(abi.encode("io.synthetix.synthetix.SystemPoolConfiguration")); - struct Data { - uint256 minLiquidityRatioD18; - uint128 __reservedForFutureUse; - uint128 preferredPool; - SetUtil.UintSet approvedPools; - } - function load() internal pure returns (Data storage systemPoolConfiguration) { - bytes32 s = _SLOT_SYSTEM_POOL_CONFIGURATION; - assembly { - systemPoolConfiguration.slot := s - } - } -} - -// @custom:artifact @synthetixio/main/contracts/storage/Vault.sol:Vault -library Vault { - struct Data { - uint256 epoch; - bytes32 __slotAvailableForFutureUse; - int128 _unused_prevTotalDebtD18; - mapping(uint256 => VaultEpoch.Data) epochData; - mapping(bytes32 => RewardDistribution.Data) rewards; - SetUtil.Bytes32Set rewardIds; - } -} - -// @custom:artifact @synthetixio/main/contracts/storage/VaultEpoch.sol:VaultEpoch -library VaultEpoch { - struct Data { - int128 unconsolidatedDebtD18; - int128 totalConsolidatedDebtD18; - Distribution.Data accountsDebtDistribution; - ScalableMapping.Data collateralAmounts; - mapping(uint256 => int256) consolidatedDebtAmountsD18; - mapping(uint128 => uint64) lastDelegationTime; - } -} - // @custom:artifact @synthetixio/oracle-manager/contracts/storage/NodeDefinition.sol:NodeDefinition library NodeDefinition { enum NodeType { @@ -415,32 +138,6 @@ library OrderFees { } } -// @custom:artifact contracts/interfaces/IAsyncOrderModule.sol:IAsyncOrderModule -interface IAsyncOrderModule { - struct SettleOrderRuntime { - uint128 marketId; - uint128 accountId; - int128 newPositionSize; - int256 pnl; - uint256 pnlUint; - uint256 amountToDeposit; - uint256 settlementReward; - bytes32 trackingCode; - } -} - -// @custom:artifact contracts/interfaces/IPerpsMarketModule.sol:IPerpsMarketModule -interface IPerpsMarketModule { - struct MarketSummary { - int256 skew; - uint256 size; - uint256 maxOpenInterest; - int currentFundingRate; - int currentFundingVelocity; - uint indexPrice; - } -} - // @custom:artifact contracts/interfaces/external/IPythVerifier.sol:IPythVerifier interface IPythVerifier { struct Price { @@ -459,6 +156,10 @@ interface IPythVerifier { // @custom:artifact contracts/modules/AsyncOrderModule.sol:AsyncOrderModule contract AsyncOrderModule { int256 public constant PRECISION = 18; + struct RuntimeCommitData { + uint feesAccrued; + AsyncOrder.Status status; + } } // @custom:artifact contracts/modules/PerpsMarketFactoryModule.sol:PerpsMarketFactoryModule @@ -469,11 +170,23 @@ contract PerpsMarketFactoryModule { // @custom:artifact contracts/storage/AsyncOrder.sol:AsyncOrder library AsyncOrder { + enum Status { + Success, + PriceOutOfBounds, + CanLiquidate, + MaxMarketValueExceeded, + MaxLeverageExceeded, + InsufficientMargin, + NotPermitted, + ZeroSizeOrder, + AcceptablePriceExceeded, + PositionFlagged + } struct Data { uint128 accountId; uint128 marketId; - int128 sizeDelta; - uint128 settlementStrategyId; + int256 sizeDelta; + uint256 settlementStrategyId; uint256 settlementTime; uint256 acceptablePrice; bytes32 trackingCode; @@ -481,22 +194,18 @@ library AsyncOrder { struct OrderCommitmentRequest { uint128 marketId; uint128 accountId; - int128 sizeDelta; - uint128 settlementStrategyId; + int256 sizeDelta; + uint256 settlementStrategyId; uint256 acceptablePrice; bytes32 trackingCode; } struct SimulateDataRuntime { uint fillPrice; - uint orderFees; + uint fees; uint availableMargin; uint currentLiquidationMargin; int128 newPositionSize; - uint newNotionalValue; - int currentAvailableMargin; - uint requiredMaintenanceMargin; - uint initialRequiredMargin; - uint totalRequiredMargin; + uint newLiquidationMargin; Position.Data newPosition; } } @@ -533,6 +242,25 @@ library GlobalPerpsMarketConfiguration { } } +// @custom:artifact contracts/storage/LiquidationConfiguration.sol:LiquidationConfiguration +library LiquidationConfiguration { + struct Data { + uint liquidationPremiumMultiplier; + uint maxLiquidationDelta; + uint maxPremiumDiscount; + uint minLiquidationRewardUsd; + uint maxLiquidationRewardUsd; + uint desiredLiquidationRewardPercentage; + uint liquidationBufferRatio; + } + function load(uint128 marketId) internal pure returns (Data storage store) { + bytes32 s = keccak256(abi.encode("io.synthetix.perps-market.LiquidationConfiguration", marketId)); + assembly { + store.slot := s + } + } +} + // @custom:artifact contracts/storage/OrderFee.sol:OrderFee library OrderFee { struct Data { @@ -544,22 +272,21 @@ library OrderFee { // @custom:artifact contracts/storage/PerpsAccount.sol:PerpsAccount library PerpsAccount { struct Data { - mapping(uint128 => uint256) collateralAmounts; - uint128 id; + mapping(uint128 => uint) collateralAmounts; SetUtil.UintSet activeCollateralTypes; SetUtil.UintSet openPositionMarketIds; + bool flaggedForLiquidation; } struct RuntimeLiquidationData { uint totalLosingPnl; - uint accumulatedLiquidationRewards; - uint liquidationReward; + uint totalLiquidationRewards; uint losingMarketsLength; uint profitableMarketsLength; uint128[] profitableMarkets; uint128[] losingMarkets; uint amountToDeposit; - uint amountToLiquidateRatioD18; - uint totalLosingPnlRatioD18; + uint amountToLiquidatePercentage; + uint percentageOfTotalLosingPnl; uint totalAvailableForDeposit; } function load(uint128 id) internal pure returns (Data storage account) { @@ -598,19 +325,23 @@ library PerpsMarket { // @custom:artifact contracts/storage/PerpsMarketConfiguration.sol:PerpsMarketConfiguration library PerpsMarketConfiguration { + enum OrderType { + ASYNC_ONCHAIN, + ASYNC_OFFCHAIN, + ATOMIC + } struct Data { - OrderFee.Data orderFees; + mapping(OrderType => OrderFee.Data) orderFees; SettlementStrategy.Data[] settlementStrategies; uint256 maxMarketValue; uint256 maxFundingVelocity; uint256 skewScale; - uint256 initialMarginRatioD18; - uint256 maintenanceMarginRatioD18; - uint256 lockedOiRatioD18; + uint256 minInitialMargin; + uint256 liquidationPremiumMultiplier; + uint256 lockedOiPercent; uint256 maxLiquidationLimitAccumulationMultiplier; - uint256 maxSecondsInLiquidationWindow; - uint256 liquidationRewardRatioD18; - uint256 minimumPositionMargin; + uint liquidationRewardPercentage; + uint maxLiquidationReward; } function load(uint128 marketId) internal pure returns (Data storage store) { bytes32 s = keccak256(abi.encode("io.synthetix.perps-market.PerpsMarketConfiguration", marketId)); @@ -628,6 +359,12 @@ library PerpsMarketFactory { address usdToken; address synthetix; address spotMarket; + mapping(uint128 => uint) maxCollateralAmounts; + uint128[] synthDeductionPriority; + uint maxLeverage; + SetUtil.UintSet liquidatableAccounts; + mapping(uint128 => uint) collateralAmounts; + mapping(uint128 => address) marketOwners; } function load() internal pure returns (Data storage perpsMarketFactory) { bytes32 s = _SLOT_PERPS_MARKET_FACTORY; @@ -663,13 +400,13 @@ library Position { // @custom:artifact contracts/storage/SettlementStrategy.sol:SettlementStrategy library SettlementStrategy { enum Type { + ONCHAIN, PYTH } struct Data { Type strategyType; uint256 settlementDelay; uint256 settlementWindowDuration; - uint256 priceWindowDuration; address priceVerificationContract; bytes32 feedId; string url; @@ -678,3 +415,8 @@ library SettlementStrategy { bool disabled; } } + +// @custom:artifact hardhat/console.sol:console +library console { + address internal constant CONSOLE_ADDRESS = address(0x000000000000000000636F6e736F6c652e6c6f67); +}