From 439f635113103a6e1b060871c454dc5ae3964cbe Mon Sep 17 00:00:00 2001 From: tahos81 Date: Wed, 12 Jun 2024 15:52:09 +0300 Subject: [PATCH 001/103] add new ABI's --- subgraph/abis/SyncStablePool.json | 950 ++++++++++++++++++++++++++++++ subgraph/abis/SyncStaking.json | 27 + 2 files changed, 977 insertions(+) create mode 100644 subgraph/abis/SyncStablePool.json create mode 100644 subgraph/abis/SyncStaking.json diff --git a/subgraph/abis/SyncStablePool.json b/subgraph/abis/SyncStablePool.json new file mode 100644 index 0000000..d867425 --- /dev/null +++ b/subgraph/abis/SyncStablePool.json @@ -0,0 +1,950 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "Expired", + "type": "error" + }, + { + "inputs": [], + "name": "InsufficientLiquidityMinted", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidSignature", + "type": "error" + }, + { + "inputs": [], + "name": "Overflow", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "liquidity", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "Burn", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "liquidity", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "Mint", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0In", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1In", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0Out", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1Out", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "Swap", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "reserve0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reserve1", + "type": "uint256" + } + ], + "name": "Sync", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + }, + { + "internalType": "address", + "name": "_sender", + "type": "address" + }, + { + "internalType": "address", + "name": "_callback", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_callbackData", + "type": "bytes" + } + ], + "name": "burn", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct IPool.TokenAmount[]", + "name": "_amounts", + "type": "tuple[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + }, + { + "internalType": "address", + "name": "_sender", + "type": "address" + }, + { + "internalType": "address", + "name": "_callback", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_callbackData", + "type": "bytes" + } + ], + "name": "burnSingle", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct IPool.TokenAmount", + "name": "_tokenAmount", + "type": "tuple" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_tokenOut", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amountOut", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_sender", + "type": "address" + } + ], + "name": "getAmountIn", + "outputs": [ + { + "internalType": "uint256", + "name": "_amountIn", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_tokenIn", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amountIn", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_sender", + "type": "address" + } + ], + "name": "getAmountOut", + "outputs": [ + { + "internalType": "uint256", + "name": "_amountOut", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getAssets", + "outputs": [ + { + "internalType": "address[]", + "name": "assets", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getProtocolFee", + "outputs": [ + { + "internalType": "uint24", + "name": "_protocolFee", + "type": "uint24" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getReserves", + "outputs": [ + { + "internalType": "uint256", + "name": "_reserve0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_reserve1", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_sender", + "type": "address" + }, + { + "internalType": "address", + "name": "_tokenIn", + "type": "address" + }, + { + "internalType": "address", + "name": "_tokenOut", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "getSwapFee", + "outputs": [ + { + "internalType": "uint24", + "name": "_swapFee", + "type": "uint24" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "invariantLast", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "master", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + }, + { + "internalType": "address", + "name": "_sender", + "type": "address" + }, + { + "internalType": "address", + "name": "_callback", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_callbackData", + "type": "bytes" + } + ], + "name": "mint", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "nonces", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_owner", + "type": "address" + }, + { + "internalType": "address", + "name": "_spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_deadline", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "_v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "_r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "_s", + "type": "bytes32" + } + ], + "name": "permit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_owner", + "type": "address" + }, + { + "internalType": "address", + "name": "_spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_deadline", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "_signature", + "type": "bytes" + } + ], + "name": "permit2", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "poolType", + "outputs": [ + { + "internalType": "uint16", + "name": "", + "type": "uint16" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "reserve0", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "reserve1", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceID", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + }, + { + "internalType": "address", + "name": "_sender", + "type": "address" + }, + { + "internalType": "address", + "name": "_callback", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_callbackData", + "type": "bytes" + } + ], + "name": "swap", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct IPool.TokenAmount", + "name": "_tokenAmount", + "type": "tuple" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "token0", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "token0PrecisionMultiplier", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "token1", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "token1PrecisionMultiplier", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_from", + "type": "address" + }, + { + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "vault", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/subgraph/abis/SyncStaking.json b/subgraph/abis/SyncStaking.json new file mode 100644 index 0000000..43b7090 --- /dev/null +++ b/subgraph/abis/SyncStaking.json @@ -0,0 +1,27 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "reward", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "ClaimRewards", + "type": "event" + } +] From 5070eddd0a2d4c5871c11625ff362f3b9c50c375 Mon Sep 17 00:00:00 2001 From: tahos81 Date: Wed, 12 Jun 2024 15:52:21 +0300 Subject: [PATCH 002/103] Update subgraph.yaml --- subgraph/subgraph.yaml | 84 ++++++++++++++++++++++++++++++++---------- 1 file changed, 64 insertions(+), 20 deletions(-) diff --git a/subgraph/subgraph.yaml b/subgraph/subgraph.yaml index ebbb0af..71dca35 100644 --- a/subgraph/subgraph.yaml +++ b/subgraph/subgraph.yaml @@ -4,26 +4,6 @@ indexerHints: schema: file: ./schema.graphql dataSources: - # - kind: ethereum - # name: KoiFactory - # network: zksync-era - # source: - # address: '0x40be1cBa6C5B47cDF9da7f963B6F761F4C60627D' - # abi: KoiFactory - # startBlock: 9672 - # mapping: - # kind: ethereum/events - # apiVersion: 0.0.7 - # language: wasm/assemblyscript - # entities: - # - KoiPair - # abis: - # - name: KoiFactory - # file: ./abis/KoiFactory.json - # eventHandlers: - # - event: PairCreated(address indexed token0, address indexed token1, bool stable, address pair, uint, uint fee) - # handler: handlePairCreated - # file: ./src/koi-factory.ts - kind: ethereum name: KoiUsdceUsdt network: zksync-era @@ -37,6 +17,7 @@ dataSources: language: wasm/assemblyscript entities: - Total + - Day - Week - Month - ClaveAccount @@ -64,6 +45,7 @@ dataSources: language: wasm/assemblyscript entities: - InAppSwap + - DailySwappedTo - WeeklySwappedTo - MonthlySwappedTo abis: @@ -86,6 +68,7 @@ dataSources: language: wasm/assemblyscript entities: - ClaveAccount + - Day - Week - Month - Total @@ -113,6 +96,7 @@ dataSources: language: wasm/assemblyscript entities: - ClaveAccount + - Day - Week - Month - Total @@ -138,6 +122,8 @@ dataSources: language: wasm/assemblyscript entities: - ClaveTransaction + - Day + - DayAccount - Week - WeekAccount - Month @@ -165,6 +151,8 @@ dataSources: language: wasm/assemblyscript entities: - ClaveTransaction + - Day + - DayAccount - Week - WeekAccount - Month @@ -192,6 +180,8 @@ dataSources: language: wasm/assemblyscript entities: - ClaveTransaction + - Day + - DayAccount - Week - WeekAccount - Month @@ -219,6 +209,8 @@ dataSources: language: wasm/assemblyscript entities: - ClaveTransaction + - Day + - DayAccount - Week - WeekAccount - Month @@ -262,6 +254,56 @@ dataSources: - event: RecoveryStopped(indexed address) handler: handleRecoveryStopped file: ./src/social-recovery.ts + - kind: ethereum + name: SyncEthWstethPool + network: zksync-era + source: + address: '0x12e7A9423d9128287E63017eE6d1f20e1C237f15' + abi: SyncStablePool + startBlock: 34062974 + mapping: + kind: ethereum/events + apiVersion: 0.0.7 + language: wasm/assemblyscript + entities: + - Total + - Day + - Week + - Month + - ClaveAccount + abis: + - name: SyncStablePool + file: ./abis/SyncStablePool.json + eventHandlers: + - event: Burn(indexed address,uint256,uint256,uint256,indexed address) + handler: handleBurn + - event: Mint(indexed address,uint256,uint256,uint256,indexed address) + handler: handleMint + file: ./src/sync-stable-pair.ts + - kind: ethereum + name: SyncStaking + network: zksync-era + source: + address: '0x2B9a7d5cD64E5c1446b32e034e75A5C93B0C8bB5' + abi: SyncStaking + startBlock: 32062974 + mapping: + kind: ethereum/events + apiVersion: 0.0.7 + language: wasm/assemblyscript + entities: + - Total + - Day + - Week + - Month + - ClaveAccount + abis: + - name: SyncStaking + file: ./abis/SyncStaking.json + eventHandlers: + - event: ClaimRewards(indexed address,indexed address,uint256) + handler: handleClaimRewards + file: ./src/sync-staking.ts templates: - kind: ethereum name: Account @@ -275,6 +317,8 @@ templates: entities: - Owner - ClaveTransaction + - Day + - DayAccount - Week - WeekAccount - Month From 16b8cca27f2cb9d520d4d062e97c4bf00b6b5465 Mon Sep 17 00:00:00 2001 From: tahos81 Date: Wed, 12 Jun 2024 15:52:25 +0300 Subject: [PATCH 003/103] Update schema.graphql --- subgraph/schema.graphql | 44 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/subgraph/schema.graphql b/subgraph/schema.graphql index 03b744f..669aeac 100644 --- a/subgraph/schema.graphql +++ b/subgraph/schema.graphql @@ -7,7 +7,26 @@ type Total @entity { backedUp: Int! gasSponsored: BigInt! invested: BigInt! + investedEth: BigInt! realizedGain: BigInt! + realizedGainEth: BigInt! +} + +type Day @entity { + "day start timestamp concat 0x0000" + id: Bytes! + createdAccounts: Int! + deployedAccounts: Int! + activeAccounts: Int! + transactions: Int! + gasSponsored: BigInt! + investIn: BigInt! + investInEth: BigInt! + investOut: BigInt! + investOutEth: BigInt! + realizedGain: BigInt! + realizedGainEth: BigInt! + swappedTo: [DailySwappedTo!]! @derivedFrom(field: "day") } type Week @entity { @@ -19,8 +38,11 @@ type Week @entity { transactions: Int! gasSponsored: BigInt! investIn: BigInt! + investInEth: BigInt! investOut: BigInt! + investOutEth: BigInt! realizedGain: BigInt! + realizedGainEth: BigInt! swappedTo: [WeeklySwappedTo!]! @derivedFrom(field: "week") } @@ -33,11 +55,22 @@ type Month @entity { transactions: Int! gasSponsored: BigInt! investIn: BigInt! + investInEth: BigInt! investOut: BigInt! + investOutEth: BigInt! realizedGain: BigInt! + realizedGainEth: BigInt! swappedTo: [MonthlySwappedTo!]! @derivedFrom(field: "month") } +type DailySwappedTo @entity { + "day.id.concat(erc20)" + id: Bytes! + day: Day! + erc20: Bytes! + amount: BigInt! +} + type WeeklySwappedTo @entity { "week.id.concat(erc20)" id: Bytes! @@ -54,6 +87,13 @@ type MonthlySwappedTo @entity { amount: BigInt! } +type DayAccount @entity(immutable: true) { + "account.id.concat(day.id)" + id: Bytes! + account: ClaveAccount! + day: Day! +} + type WeekAccount @entity(immutable: true) { "account.id.concat(week.id)" id: Bytes! @@ -80,10 +120,14 @@ type ClaveAccount @entity { "account implementation address" implementation: Bytes invested: BigInt! + investedEth: BigInt! realizedGain: BigInt! + realizedGainEth: BigInt! transactions: [ClaveTransaction!]! @derivedFrom(field: "sender") inAppSwaps: [InAppSwap!]! @derivedFrom(field: "account") + activeDays: [DayAccount!]! @derivedFrom(field: "account") activeWeeks: [WeekAccount!]! @derivedFrom(field: "account") + activeMonths: [MonthAccount!]! @derivedFrom(field: "account") } enum Paymaster { From 252966f5fc67e23cb83a847dc1c2e64866103fe1 Mon Sep 17 00:00:00 2001 From: tahos81 Date: Wed, 12 Jun 2024 15:52:39 +0300 Subject: [PATCH 004/103] feat: update old mappers --- subgraph/src/account-factory-v2.ts | 20 ++++++++- subgraph/src/account-factory.ts | 18 ++++++++- subgraph/src/account.ts | 11 ++++- subgraph/src/erc-20-paymaster.ts | 7 ++++ subgraph/src/gasless-paymaster.ts | 8 ++++ subgraph/src/helpers.ts | 65 ++++++++++++++++++++++++++++-- subgraph/src/koi-pair.ts | 16 +++++++- subgraph/src/odos-router.ts | 21 +++++++++- 8 files changed, 156 insertions(+), 10 deletions(-) diff --git a/subgraph/src/account-factory-v2.ts b/subgraph/src/account-factory-v2.ts index a08daa5..b455424 100644 --- a/subgraph/src/account-factory-v2.ts +++ b/subgraph/src/account-factory-v2.ts @@ -10,10 +10,16 @@ import { Bytes } from '@graphprotocol/graph-ts'; import { ClaveAccountCreated as ClaveAccountCreatedEvent, ClaveAccountDeployed as ClaveAccountDeployedEvent, -} from '../generated/Contract/Contract'; +} from '../generated/AccountFactoryV2/AccountFactoryV2'; import { ClaveAccount } from '../generated/schema'; import { Account } from '../generated/templates'; -import { ZERO, getOrCreateMonth, getOrCreateWeek, getTotal } from './helpers'; +import { + ZERO, + getOrCreateDay, + getOrCreateMonth, + getOrCreateWeek, + getTotal, +} from './helpers'; export function handleClaveAccountCreated( event: ClaveAccountCreatedEvent, @@ -23,10 +29,12 @@ export function handleClaveAccountCreated( return; } account = new ClaveAccount(event.params.accountAddress); + const day = getOrCreateDay(event.block.timestamp); const week = getOrCreateWeek(event.block.timestamp); const month = getOrCreateMonth(event.block.timestamp); const total = getTotal(); + day.createdAccounts = day.createdAccounts + 1; week.createdAccounts = week.createdAccounts + 1; month.createdAccounts = month.createdAccounts + 1; total.createdAccounts = total.createdAccounts + 1; @@ -37,7 +45,10 @@ export function handleClaveAccountCreated( account.txCount = 0; account.invested = ZERO; account.realizedGain = ZERO; + account.investedEth = ZERO; + account.realizedGainEth = ZERO; + day.save(); week.save(); month.save(); total.save(); @@ -49,9 +60,11 @@ export function handleClaveAccountDeployed( event: ClaveAccountDeployedEvent, ): void { let account = ClaveAccount.load(event.params.accountAddress); + const day = getOrCreateDay(event.block.timestamp); const week = getOrCreateWeek(event.block.timestamp); const month = getOrCreateMonth(event.block.timestamp); const total = getTotal(); + day.deployedAccounts = day.deployedAccounts + 1; week.deployedAccounts = week.deployedAccounts + 1; month.deployedAccounts = month.deployedAccounts + 1; total.deployedAccounts = total.deployedAccounts + 1; @@ -65,6 +78,8 @@ export function handleClaveAccountDeployed( account.creationDate = ZERO; account.invested = ZERO; account.realizedGain = ZERO; + account.investedEth = ZERO; + account.realizedGainEth = ZERO; } account.implementation = Bytes.fromHexString( @@ -72,6 +87,7 @@ export function handleClaveAccountDeployed( ); account.deployDate = event.block.timestamp; + day.save(); week.save(); month.save(); total.save(); diff --git a/subgraph/src/account-factory.ts b/subgraph/src/account-factory.ts index aa45854..7b4f563 100644 --- a/subgraph/src/account-factory.ts +++ b/subgraph/src/account-factory.ts @@ -18,7 +18,13 @@ import { NewClaveAccount as NewClaveAccountEvent } from '../generated/AccountFac import { ClaveAccount } from '../generated/schema'; import { Account } from '../generated/templates'; import { wallets } from '../wallets'; -import { ZERO, getOrCreateMonth, getOrCreateWeek, getTotal } from './helpers'; +import { + ZERO, + getOrCreateDay, + getOrCreateMonth, + getOrCreateWeek, + getTotal, +} from './helpers'; // eslint-disable-next-line @typescript-eslint/no-unused-vars export function handleOnce(_block: ethereum.Block): void { @@ -30,10 +36,12 @@ export function handleOnce(_block: ethereum.Block): void { Date.parse(createdAt).getTime(), ).div(BigInt.fromU32(1000)); const account = new ClaveAccount(Bytes.fromHexString(accountAddress)); + const day = getOrCreateDay(createdAtDate); const week = getOrCreateWeek(createdAtDate); const month = getOrCreateMonth(createdAtDate); const total = getTotal(); + day.createdAccounts = day.createdAccounts + 1; week.createdAccounts = week.createdAccounts + 1; month.createdAccounts = month.createdAccounts + 1; total.createdAccounts = total.createdAccounts + 1; @@ -44,7 +52,10 @@ export function handleOnce(_block: ethereum.Block): void { account.txCount = 0; account.invested = ZERO; account.realizedGain = ZERO; + account.investedEth = ZERO; + account.realizedGainEth = ZERO; + day.save(); week.save(); month.save(); total.save(); @@ -55,9 +66,11 @@ export function handleOnce(_block: ethereum.Block): void { export function handleNewClaveAccount(event: NewClaveAccountEvent): void { let account = ClaveAccount.load(event.params.accountAddress); + const day = getOrCreateDay(event.block.timestamp); const week = getOrCreateWeek(event.block.timestamp); const month = getOrCreateMonth(event.block.timestamp); const total = getTotal(); + day.createdAccounts = day.createdAccounts + 1; week.deployedAccounts = week.deployedAccounts + 1; month.deployedAccounts = month.deployedAccounts + 1; total.deployedAccounts = total.deployedAccounts + 1; @@ -71,12 +84,15 @@ export function handleNewClaveAccount(event: NewClaveAccountEvent): void { account.creationDate = ZERO; account.invested = ZERO; account.realizedGain = ZERO; + account.investedEth = ZERO; + account.realizedGainEth = ZERO; } account.implementation = Bytes.fromHexString( '0xdd4dD37B22Fc16DBFF3daB6Ecd681798c459f275', ); account.deployDate = event.block.timestamp; + day.save(); week.save(); month.save(); total.save(); diff --git a/subgraph/src/account.ts b/subgraph/src/account.ts index e0b900a..eb29c07 100644 --- a/subgraph/src/account.ts +++ b/subgraph/src/account.ts @@ -7,12 +7,14 @@ /* eslint-disable @typescript-eslint/consistent-type-imports */ import { BigInt } from '@graphprotocol/graph-ts'; +import { ClaveAccount, ClaveTransaction } from '../generated/schema'; import { FeePaid as FeePaidEvent, Upgraded as UpgradedEvent, -} from '../generated/ClaveImplementation/ClaveImplementation'; -import { ClaveAccount, ClaveTransaction } from '../generated/schema'; +} from '../generated/templates/Account/ClaveImplementation'; import { + getOrCreateDay, + getOrCreateDayAccount, getOrCreateMonth, getOrCreateMonthAccount, getOrCreateWeek, @@ -22,11 +24,14 @@ import { export function handleFeePaid(event: FeePaidEvent): void { const transaction = new ClaveTransaction(event.transaction.hash); + const day = getOrCreateDay(event.block.timestamp); const week = getOrCreateWeek(event.block.timestamp); const month = getOrCreateMonth(event.block.timestamp); const total = getTotal(); const account = ClaveAccount.load(event.address); if (account != null) { + const dayAccount = getOrCreateDayAccount(account, day); + dayAccount.save(); const weekAccount = getOrCreateWeekAccount(account, week); weekAccount.save(); const monthAccount = getOrCreateMonthAccount(account, month); @@ -35,6 +40,7 @@ export function handleFeePaid(event: FeePaidEvent): void { account.txCount = account.txCount + 1; account.save(); } + day.transactions = day.transactions + 1; week.transactions = week.transactions + 1; month.transactions = month.transactions + 1; total.transactions = total.transactions + 1; @@ -51,6 +57,7 @@ export function handleFeePaid(event: FeePaidEvent): void { transaction.paymaster = 'None'; transaction.date = event.block.timestamp; + day.save(); week.save(); month.save(); total.save(); diff --git a/subgraph/src/erc-20-paymaster.ts b/subgraph/src/erc-20-paymaster.ts index acaf2e7..39330ce 100644 --- a/subgraph/src/erc-20-paymaster.ts +++ b/subgraph/src/erc-20-paymaster.ts @@ -10,6 +10,8 @@ import { BigInt } from '@graphprotocol/graph-ts'; import { ERC20PaymasterUsed as ERC20PaymasterUsedEvent } from '../generated/ERC20Paymaster/ERC20Paymaster'; import { ClaveAccount, ClaveTransaction } from '../generated/schema'; import { + getOrCreateDay, + getOrCreateDayAccount, getOrCreateMonth, getOrCreateMonthAccount, getOrCreateWeek, @@ -23,13 +25,17 @@ export function handleERC20PaymasterUsed(event: ERC20PaymasterUsedEvent): void { return; } + const day = getOrCreateDay(event.block.timestamp); const week = getOrCreateWeek(event.block.timestamp); const month = getOrCreateMonth(event.block.timestamp); const total = getTotal(); + day.transactions = day.transactions + 1; week.transactions = week.transactions + 1; month.transactions = month.transactions + 1; total.transactions = total.transactions + 1; + const dayAccount = getOrCreateDayAccount(account, day); + dayAccount.save(); const weekAccount = getOrCreateWeekAccount(account, week); weekAccount.save(); const monthAccount = getOrCreateMonthAccount(account, month); @@ -52,6 +58,7 @@ export function handleERC20PaymasterUsed(event: ERC20PaymasterUsedEvent): void { account.txCount = account.txCount + 1; account.save(); + day.save(); week.save(); month.save(); total.save(); diff --git a/subgraph/src/gasless-paymaster.ts b/subgraph/src/gasless-paymaster.ts index 8d25d7a..f36a252 100644 --- a/subgraph/src/gasless-paymaster.ts +++ b/subgraph/src/gasless-paymaster.ts @@ -10,6 +10,8 @@ import { BigInt } from '@graphprotocol/graph-ts'; import { FeeSponsored as FeeSponsoredEvent } from '../generated/GaslessPaymaster/GaslessPaymaster'; import { ClaveAccount, ClaveTransaction } from '../generated/schema'; import { + getOrCreateDay, + getOrCreateDayAccount, getOrCreateMonth, getOrCreateMonthAccount, getOrCreateWeek, @@ -19,16 +21,20 @@ import { export function handleFeeSponsored(event: FeeSponsoredEvent): void { const transaction = new ClaveTransaction(event.transaction.hash); + const day = getOrCreateDay(event.block.timestamp); const week = getOrCreateWeek(event.block.timestamp); const month = getOrCreateMonth(event.block.timestamp); const total = getTotal(); const account = ClaveAccount.load(event.params.user); if (account != null) { + const dayAccount = getOrCreateDayAccount(account, day); + dayAccount.save(); const weekAccount = getOrCreateWeekAccount(account, week); weekAccount.save(); const monthAccount = getOrCreateMonthAccount(account, month); monthAccount.save(); + day.transactions = day.transactions + 1; week.transactions = week.transactions + 1; month.transactions = month.transactions + 1; total.transactions = total.transactions + 1; @@ -44,6 +50,7 @@ export function handleFeeSponsored(event: FeeSponsoredEvent): void { gasUsed = receipt.gasUsed; } const gasCost = event.transaction.gasPrice.times(gasUsed); + day.gasSponsored = day.gasSponsored.plus(gasCost); week.gasSponsored = week.gasSponsored.plus(gasCost); month.gasSponsored = month.gasSponsored.plus(gasCost); total.gasSponsored = total.gasSponsored.plus(gasCost); @@ -55,6 +62,7 @@ export function handleFeeSponsored(event: FeeSponsoredEvent): void { account.save(); } + day.save(); week.save(); month.save(); total.save(); diff --git a/subgraph/src/helpers.ts b/subgraph/src/helpers.ts index 5f70bdc..878cdb6 100644 --- a/subgraph/src/helpers.ts +++ b/subgraph/src/helpers.ts @@ -14,6 +14,8 @@ import { BigInt } from '@graphprotocol/graph-ts'; import { ClaveAccount, + Day, + DayAccount, Month, MonthAccount, Total, @@ -23,10 +25,59 @@ import { export const ZERO = BigInt.fromI32(0); export const ONE = BigInt.fromI32(1); -const START_TIMESTAMP = BigInt.fromU32(1706045075); +const START_TIMESTAMP = BigInt.fromU32(1705881660); +const MONTH_START_TIMESTAMP = BigInt.fromU32(1704067260); +const DAY = BigInt.fromU32(86_400); const WEEK = BigInt.fromU32(604_800); const MONTH = BigInt.fromU32(2_592_000); +export function getOrCreateDay(timestamp: BigInt): Day { + let dayNumber = timestamp.minus(START_TIMESTAMP).div(DAY); + let dayId = Bytes.fromByteArray( + Bytes.fromBigInt(START_TIMESTAMP.plus(dayNumber.times(DAY))), + ).concat(Bytes.fromHexString('0x0000')); + let day = Day.load(dayId); + + if (day !== null) { + return day; + } + + day = new Day(dayId); + day.createdAccounts = 0; + day.deployedAccounts = 0; + day.activeAccounts = 0; + day.transactions = 0; + day.investIn = ZERO; + day.investInEth = ZERO; + day.investOut = ZERO; + day.investOutEth = ZERO; + day.realizedGain = ZERO; + day.realizedGainEth = ZERO; + day.gasSponsored = ZERO; + + return day; +} + +export function getOrCreateDayAccount( + account: ClaveAccount, + day: Day, +): DayAccount { + let dayAccountId = account.id.concat(day.id); + let dayAccount = DayAccount.load(dayAccountId); + + if (dayAccount != null) { + return dayAccount; + } + + day.activeAccounts = day.activeAccounts + 1; + + dayAccount = new DayAccount(dayAccountId); + dayAccount.account = account.id; + dayAccount.day = day.id; + + return dayAccount; +} + export function getOrCreateWeek(timestamp: BigInt): Week { let weekNumber = timestamp.minus(START_TIMESTAMP).div(WEEK); let weekId = Bytes.fromByteArray( @@ -44,8 +95,11 @@ export function getOrCreateWeek(timestamp: BigInt): Week { week.activeAccounts = 0; week.transactions = 0; week.investIn = ZERO; + week.investInEth = ZERO; week.investOut = ZERO; + week.investOutEth = ZERO; week.realizedGain = ZERO; + week.realizedGainEth = ZERO; week.gasSponsored = ZERO; return week; @@ -72,9 +126,9 @@ export function getOrCreateWeekAccount( } export function getOrCreateMonth(timestamp: BigInt): Month { - let monthNumber = timestamp.minus(START_TIMESTAMP).div(MONTH); + let monthNumber = timestamp.minus(MONTH_START_TIMESTAMP).div(MONTH); let monthId = Bytes.fromByteArray( - Bytes.fromBigInt(START_TIMESTAMP.plus(monthNumber.times(MONTH))), + Bytes.fromBigInt(MONTH_START_TIMESTAMP.plus(monthNumber.times(MONTH))), ).concat(Bytes.fromHexString('0x00')); let month = Month.load(monthId); @@ -88,8 +142,11 @@ export function getOrCreateMonth(timestamp: BigInt): Month { month.activeAccounts = 0; month.transactions = 0; month.investIn = ZERO; + month.investInEth = ZERO; month.investOut = ZERO; + month.investOutEth = ZERO; month.realizedGain = ZERO; + month.realizedGainEth = ZERO; month.gasSponsored = ZERO; return month; @@ -129,7 +186,9 @@ export function getTotal(): Total { total.transactions = 0; total.backedUp = 0; total.invested = ZERO; + total.investedEth = ZERO; total.realizedGain = ZERO; + total.realizedGainEth = ZERO; total.gasSponsored = ZERO; return total; diff --git a/subgraph/src/koi-pair.ts b/subgraph/src/koi-pair.ts index 96da83c..7a34727 100644 --- a/subgraph/src/koi-pair.ts +++ b/subgraph/src/koi-pair.ts @@ -11,7 +11,12 @@ import { Mint as MintEvent, } from '../generated/KoiUsdceUsdt/KoiPair'; import { ClaveAccount } from '../generated/schema'; -import { getOrCreateMonth, getOrCreateWeek, getTotal } from './helpers'; +import { + getOrCreateDay, + getOrCreateMonth, + getOrCreateWeek, + getTotal, +} from './helpers'; export function handleMint(event: MintEvent): void { const account = ClaveAccount.load(event.transaction.from); @@ -21,15 +26,18 @@ export function handleMint(event: MintEvent): void { const amount = event.params.amount1.plus(event.params.amount0); + const day = getOrCreateDay(event.block.timestamp); const week = getOrCreateWeek(event.block.timestamp); const month = getOrCreateMonth(event.block.timestamp); const total = getTotal(); + day.investIn = day.investIn.plus(amount); week.investIn = week.investIn.plus(amount); month.investIn = month.investIn.plus(amount); total.invested = total.invested.plus(amount); account.invested = account.invested.plus(amount); + day.save(); week.save(); month.save(); total.save(); @@ -44,15 +52,18 @@ export function handleBurn(event: BurnEvent): void { const amount = event.params.amount1.plus(event.params.amount0); + const day = getOrCreateDay(event.block.timestamp); const week = getOrCreateWeek(event.block.timestamp); const month = getOrCreateMonth(event.block.timestamp); const total = getTotal(); + day.investOut = day.investOut.plus(amount); week.investOut = week.investOut.plus(amount); month.investOut = month.investOut.plus(amount); total.invested = total.invested.minus(amount); account.invested = account.invested.minus(amount); + day.save(); week.save(); month.save(); total.save(); @@ -67,15 +78,18 @@ export function handleClaim(event: ClaimEvent): void { const amount = event.params.amount1.plus(event.params.amount0); + const day = getOrCreateDay(event.block.timestamp); const week = getOrCreateWeek(event.block.timestamp); const month = getOrCreateMonth(event.block.timestamp); const total = getTotal(); + day.realizedGain = day.realizedGain.plus(amount); week.realizedGain = week.realizedGain.plus(amount); month.realizedGain = month.realizedGain.plus(amount); total.realizedGain = total.realizedGain.plus(amount); account.realizedGain = account.realizedGain.plus(amount); + day.save(); week.save(); month.save(); total.save(); diff --git a/subgraph/src/odos-router.ts b/subgraph/src/odos-router.ts index 031fc51..ecc7e3b 100644 --- a/subgraph/src/odos-router.ts +++ b/subgraph/src/odos-router.ts @@ -8,11 +8,17 @@ import { Swap as SwapEvent } from '../generated/OdosRouter/OdosRouter'; import { ClaveAccount, + DailySwappedTo, InAppSwap, MonthlySwappedTo, WeeklySwappedTo, } from '../generated/schema'; -import { ZERO, getOrCreateMonth, getOrCreateWeek } from './helpers'; +import { + ZERO, + getOrCreateDay, + getOrCreateMonth, + getOrCreateWeek, +} from './helpers'; export function handleSwap(event: SwapEvent): void { const account = ClaveAccount.load(event.params.sender); @@ -20,10 +26,22 @@ export function handleSwap(event: SwapEvent): void { return; } + const day = getOrCreateDay(event.block.timestamp); const week = getOrCreateWeek(event.block.timestamp); const month = getOrCreateMonth(event.block.timestamp); const tokenOutAddress = event.params.outputToken; + const dailySwappedToId = day.id.concat(tokenOutAddress); + let dailySwappedTo = DailySwappedTo.load(dailySwappedToId); + if (!dailySwappedTo) { + dailySwappedTo = new DailySwappedTo(dailySwappedToId); + dailySwappedTo.day = day.id; + dailySwappedTo.erc20 = tokenOutAddress; + dailySwappedTo.amount = ZERO; + } + + dailySwappedTo.amount = dailySwappedTo.amount.plus(event.params.amountOut); + const weeklySwappedToId = week.id.concat(tokenOutAddress); let weeklySwappedTo = WeeklySwappedTo.load(weeklySwappedToId); if (!weeklySwappedTo) { @@ -61,6 +79,7 @@ export function handleSwap(event: SwapEvent): void { inAppSwap.tokenOut = event.params.outputToken; inAppSwap.date = event.block.timestamp; + dailySwappedTo.save(); weeklySwappedTo.save(); monthlySwappedTo.save(); inAppSwap.save(); From bed8f6233c41f30fdc9291a7302d3a6ca4a8d156 Mon Sep 17 00:00:00 2001 From: tahos81 Date: Wed, 12 Jun 2024 15:52:44 +0300 Subject: [PATCH 005/103] feat: new mappers --- subgraph/src/sync-stable-pair.ts | 64 ++++++++++++++++++++++++++++++++ subgraph/src/sync-staking.ts | 35 +++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 subgraph/src/sync-stable-pair.ts create mode 100644 subgraph/src/sync-staking.ts diff --git a/subgraph/src/sync-stable-pair.ts b/subgraph/src/sync-stable-pair.ts new file mode 100644 index 0000000..881817f --- /dev/null +++ b/subgraph/src/sync-stable-pair.ts @@ -0,0 +1,64 @@ +/* eslint-disable @typescript-eslint/consistent-type-imports */ +import { + Burn as BurnEvent, + Mint as MintEvent, +} from '../generated/SyncEthWstethPool/SyncStablePool'; +import { ClaveAccount } from '../generated/schema'; +import { + getOrCreateDay, + getOrCreateMonth, + getOrCreateWeek, + getTotal, +} from './helpers'; + +export function handleMint(event: MintEvent): void { + const account = ClaveAccount.load(event.transaction.from); + if (!account) { + return; + } + + const amount = event.params.amount1.plus(event.params.amount0); + + const day = getOrCreateDay(event.block.timestamp); + const week = getOrCreateWeek(event.block.timestamp); + const month = getOrCreateMonth(event.block.timestamp); + const total = getTotal(); + + day.investInEth = day.investInEth.plus(amount); + week.investInEth = week.investInEth.plus(amount); + month.investInEth = month.investInEth.plus(amount); + total.investedEth = total.investedEth.plus(amount); + account.investedEth = account.investedEth.plus(amount); + + day.save(); + week.save(); + month.save(); + total.save(); + account.save(); +} + +export function handleBurn(event: BurnEvent): void { + const account = ClaveAccount.load(event.transaction.from); + if (!account) { + return; + } + + const amount = event.params.amount1.plus(event.params.amount0); + + const day = getOrCreateDay(event.block.timestamp); + const week = getOrCreateWeek(event.block.timestamp); + const month = getOrCreateMonth(event.block.timestamp); + const total = getTotal(); + + day.investOutEth = day.investOutEth.plus(amount); + week.investOutEth = week.investOutEth.plus(amount); + month.investOutEth = month.investOutEth.plus(amount); + total.investedEth = total.investedEth.minus(amount); + account.investedEth = account.investedEth.minus(amount); + + day.save(); + week.save(); + month.save(); + total.save(); + account.save(); +} diff --git a/subgraph/src/sync-staking.ts b/subgraph/src/sync-staking.ts new file mode 100644 index 0000000..3c2681c --- /dev/null +++ b/subgraph/src/sync-staking.ts @@ -0,0 +1,35 @@ +/* eslint-disable @typescript-eslint/consistent-type-imports */ +import { ClaimRewards as ClaimRewardsEvent } from '../generated/SyncStaking/SyncStaking'; +import { ClaveAccount } from '../generated/schema'; +import { + getOrCreateDay, + getOrCreateMonth, + getOrCreateWeek, + getTotal, +} from './helpers'; + +export function handleClaimRewards(event: ClaimRewardsEvent): void { + const account = ClaveAccount.load(event.params.from); + if (!account) { + return; + } + + const amount = event.params.amount; + + const day = getOrCreateDay(event.block.timestamp); + const week = getOrCreateWeek(event.block.timestamp); + const month = getOrCreateMonth(event.block.timestamp); + const total = getTotal(); + + day.realizedGainEth = day.realizedGainEth.plus(amount); + week.realizedGainEth = week.realizedGainEth.plus(amount); + month.realizedGainEth = month.realizedGainEth.plus(amount); + total.realizedGainEth = total.realizedGainEth.plus(amount); + account.realizedGainEth = account.realizedGainEth.plus(amount); + + day.save(); + week.save(); + month.save(); + total.save(); + account.save(); +} From 8c1fbd5a6cb1de647207c0a854a2ebbf6957f09b Mon Sep 17 00:00:00 2001 From: tahos81 Date: Wed, 12 Jun 2024 15:52:58 +0300 Subject: [PATCH 006/103] husky --- subgraph/src/sync-stable-pair.ts | 5 +++++ subgraph/src/sync-staking.ts | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/subgraph/src/sync-stable-pair.ts b/subgraph/src/sync-stable-pair.ts index 881817f..244acdf 100644 --- a/subgraph/src/sync-stable-pair.ts +++ b/subgraph/src/sync-stable-pair.ts @@ -1,3 +1,8 @@ +/** + * Copyright Clave - All Rights Reserved + * Unauthorized copying of this file, via any medium is strictly prohibited + * Proprietary and confidential + */ /* eslint-disable @typescript-eslint/consistent-type-imports */ import { Burn as BurnEvent, diff --git a/subgraph/src/sync-staking.ts b/subgraph/src/sync-staking.ts index 3c2681c..c80ce42 100644 --- a/subgraph/src/sync-staking.ts +++ b/subgraph/src/sync-staking.ts @@ -1,3 +1,8 @@ +/** + * Copyright Clave - All Rights Reserved + * Unauthorized copying of this file, via any medium is strictly prohibited + * Proprietary and confidential + */ /* eslint-disable @typescript-eslint/consistent-type-imports */ import { ClaimRewards as ClaimRewardsEvent } from '../generated/SyncStaking/SyncStaking'; import { ClaveAccount } from '../generated/schema'; From 1c07cf81aabb26a2a4995ef1c1fb83847fb1c470 Mon Sep 17 00:00:00 2001 From: tahos81 Date: Wed, 12 Jun 2024 16:20:22 +0300 Subject: [PATCH 007/103] feat: zerolend init --- subgraph/abis/zeroUsdtPool.json | 1227 +++++++++++++++++++++++++++++++ subgraph/networks.json | 3 + subgraph/src/zero-usdt-pool.ts | 38 + subgraph/subgraph.yaml | 26 + 4 files changed, 1294 insertions(+) create mode 100644 subgraph/abis/zeroUsdtPool.json create mode 100644 subgraph/src/zero-usdt-pool.ts diff --git a/subgraph/abis/zeroUsdtPool.json b/subgraph/abis/zeroUsdtPool.json new file mode 100644 index 0000000..7b87c8a --- /dev/null +++ b/subgraph/abis/zeroUsdtPool.json @@ -0,0 +1,1227 @@ +[ + { + "inputs": [ + { + "internalType": "contract IPoolAddressesProvider", + "name": "provider", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "reserve", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "backer", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "fee", + "type": "uint256" + } + ], + "name": "BackUnbacked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "reserve", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "onBehalfOf", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "enum DataTypes.InterestRateMode", + "name": "interestRateMode", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "borrowRate", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint16", + "name": "referralCode", + "type": "uint16" + } + ], + "name": "Borrow", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "initiator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "enum DataTypes.InterestRateMode", + "name": "interestRateMode", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "premium", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint16", + "name": "referralCode", + "type": "uint16" + } + ], + "name": "FlashLoan", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "totalDebt", + "type": "uint256" + } + ], + "name": "IsolationModeTotalDebtUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "collateralAsset", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "debtAsset", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "debtToCover", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "liquidatedCollateralAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "liquidator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "receiveAToken", + "type": "bool" + } + ], + "name": "LiquidationCall", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "reserve", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "onBehalfOf", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint16", + "name": "referralCode", + "type": "uint16" + } + ], + "name": "MintUnbacked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "reserve", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountMinted", + "type": "uint256" + } + ], + "name": "MintedToTreasury", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "reserve", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + } + ], + "name": "RebalanceStableBorrowRate", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "reserve", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "repayer", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bool", + "name": "useATokens", + "type": "bool" + } + ], + "name": "Repay", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "reserve", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "liquidityRate", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "stableBorrowRate", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "variableBorrowRate", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "liquidityIndex", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "variableBorrowIndex", + "type": "uint256" + } + ], + "name": "ReserveDataUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "reserve", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + } + ], + "name": "ReserveUsedAsCollateralDisabled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "reserve", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + } + ], + "name": "ReserveUsedAsCollateralEnabled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "reserve", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "onBehalfOf", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint16", + "name": "referralCode", + "type": "uint16" + } + ], + "name": "Supply", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "reserve", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": false, + "internalType": "enum DataTypes.InterestRateMode", + "name": "interestRateMode", + "type": "uint8" + } + ], + "name": "SwapBorrowRateMode", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "categoryId", + "type": "uint8" + } + ], + "name": "UserEModeSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "reserve", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Withdraw", + "type": "event" + }, + { + "inputs": [], + "name": "ADDRESSES_PROVIDER", + "outputs": [ + { + "internalType": "contract IPoolAddressesProvider", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "BRIDGE_PROTOCOL_FEE", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "FLASHLOAN_PREMIUM_TOTAL", + "outputs": [{ "internalType": "uint128", "name": "", "type": "uint128" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "FLASHLOAN_PREMIUM_TO_PROTOCOL", + "outputs": [{ "internalType": "uint128", "name": "", "type": "uint128" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_NUMBER_RESERVES", + "outputs": [{ "internalType": "uint16", "name": "", "type": "uint16" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_STABLE_RATE_BORROW_SIZE_PERCENT", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "POOL_REVISION", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "asset", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" }, + { "internalType": "uint256", "name": "fee", "type": "uint256" } + ], + "name": "backUnbacked", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "asset", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" }, + { + "internalType": "uint256", + "name": "interestRateMode", + "type": "uint256" + }, + { "internalType": "uint16", "name": "referralCode", "type": "uint16" }, + { "internalType": "address", "name": "onBehalfOf", "type": "address" } + ], + "name": "borrow", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint8", "name": "id", "type": "uint8" }, + { + "components": [ + { "internalType": "uint16", "name": "ltv", "type": "uint16" }, + { + "internalType": "uint16", + "name": "liquidationThreshold", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "liquidationBonus", + "type": "uint16" + }, + { + "internalType": "address", + "name": "priceSource", + "type": "address" + }, + { "internalType": "string", "name": "label", "type": "string" } + ], + "internalType": "struct DataTypes.EModeCategory", + "name": "category", + "type": "tuple" + } + ], + "name": "configureEModeCategory", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "asset", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" }, + { "internalType": "address", "name": "onBehalfOf", "type": "address" }, + { "internalType": "uint16", "name": "referralCode", "type": "uint16" } + ], + "name": "deposit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "asset", "type": "address" } + ], + "name": "dropReserve", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "asset", "type": "address" }, + { "internalType": "address", "name": "from", "type": "address" }, + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" }, + { + "internalType": "uint256", + "name": "balanceFromBefore", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "balanceToBefore", + "type": "uint256" + } + ], + "name": "finalizeTransfer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiverAddress", + "type": "address" + }, + { "internalType": "address[]", "name": "assets", "type": "address[]" }, + { "internalType": "uint256[]", "name": "amounts", "type": "uint256[]" }, + { + "internalType": "uint256[]", + "name": "interestRateModes", + "type": "uint256[]" + }, + { "internalType": "address", "name": "onBehalfOf", "type": "address" }, + { "internalType": "bytes", "name": "params", "type": "bytes" }, + { "internalType": "uint16", "name": "referralCode", "type": "uint16" } + ], + "name": "flashLoan", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiverAddress", + "type": "address" + }, + { "internalType": "address", "name": "asset", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" }, + { "internalType": "bytes", "name": "params", "type": "bytes" }, + { "internalType": "uint16", "name": "referralCode", "type": "uint16" } + ], + "name": "flashLoanSimple", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "asset", "type": "address" } + ], + "name": "getConfiguration", + "outputs": [ + { + "components": [ + { "internalType": "uint256", "name": "data", "type": "uint256" } + ], + "internalType": "struct DataTypes.ReserveConfigurationMap", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint8", "name": "id", "type": "uint8" }], + "name": "getEModeCategoryData", + "outputs": [ + { + "components": [ + { "internalType": "uint16", "name": "ltv", "type": "uint16" }, + { + "internalType": "uint16", + "name": "liquidationThreshold", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "liquidationBonus", + "type": "uint16" + }, + { + "internalType": "address", + "name": "priceSource", + "type": "address" + }, + { "internalType": "string", "name": "label", "type": "string" } + ], + "internalType": "struct DataTypes.EModeCategory", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint16", "name": "id", "type": "uint16" }], + "name": "getReserveAddressById", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "asset", "type": "address" } + ], + "name": "getReserveData", + "outputs": [ + { + "components": [ + { + "components": [ + { "internalType": "uint256", "name": "data", "type": "uint256" } + ], + "internalType": "struct DataTypes.ReserveConfigurationMap", + "name": "configuration", + "type": "tuple" + }, + { + "internalType": "uint128", + "name": "liquidityIndex", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "currentLiquidityRate", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "variableBorrowIndex", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "currentVariableBorrowRate", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "currentStableBorrowRate", + "type": "uint128" + }, + { + "internalType": "uint40", + "name": "lastUpdateTimestamp", + "type": "uint40" + }, + { "internalType": "uint16", "name": "id", "type": "uint16" }, + { + "internalType": "address", + "name": "aTokenAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "stableDebtTokenAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "variableDebtTokenAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "interestRateStrategyAddress", + "type": "address" + }, + { + "internalType": "uint128", + "name": "accruedToTreasury", + "type": "uint128" + }, + { "internalType": "uint128", "name": "unbacked", "type": "uint128" }, + { + "internalType": "uint128", + "name": "isolationModeTotalDebt", + "type": "uint128" + } + ], + "internalType": "struct DataTypes.ReserveData", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "asset", "type": "address" } + ], + "name": "getReserveNormalizedIncome", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "asset", "type": "address" } + ], + "name": "getReserveNormalizedVariableDebt", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getReservesList", + "outputs": [ + { "internalType": "address[]", "name": "", "type": "address[]" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "user", "type": "address" } + ], + "name": "getUserAccountData", + "outputs": [ + { + "internalType": "uint256", + "name": "totalCollateralBase", + "type": "uint256" + }, + { "internalType": "uint256", "name": "totalDebtBase", "type": "uint256" }, + { + "internalType": "uint256", + "name": "availableBorrowsBase", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "currentLiquidationThreshold", + "type": "uint256" + }, + { "internalType": "uint256", "name": "ltv", "type": "uint256" }, + { "internalType": "uint256", "name": "healthFactor", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "user", "type": "address" } + ], + "name": "getUserConfiguration", + "outputs": [ + { + "components": [ + { "internalType": "uint256", "name": "data", "type": "uint256" } + ], + "internalType": "struct DataTypes.UserConfigurationMap", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "user", "type": "address" } + ], + "name": "getUserEMode", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "asset", "type": "address" }, + { "internalType": "address", "name": "aTokenAddress", "type": "address" }, + { + "internalType": "address", + "name": "stableDebtAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "variableDebtAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "interestRateStrategyAddress", + "type": "address" + } + ], + "name": "initReserve", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IPoolAddressesProvider", + "name": "provider", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "collateralAsset", + "type": "address" + }, + { "internalType": "address", "name": "debtAsset", "type": "address" }, + { "internalType": "address", "name": "user", "type": "address" }, + { "internalType": "uint256", "name": "debtToCover", "type": "uint256" }, + { "internalType": "bool", "name": "receiveAToken", "type": "bool" } + ], + "name": "liquidationCall", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address[]", "name": "assets", "type": "address[]" } + ], + "name": "mintToTreasury", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "asset", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" }, + { "internalType": "address", "name": "onBehalfOf", "type": "address" }, + { "internalType": "uint16", "name": "referralCode", "type": "uint16" } + ], + "name": "mintUnbacked", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "asset", "type": "address" }, + { "internalType": "address", "name": "user", "type": "address" } + ], + "name": "rebalanceStableBorrowRate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "asset", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" }, + { + "internalType": "uint256", + "name": "interestRateMode", + "type": "uint256" + }, + { "internalType": "address", "name": "onBehalfOf", "type": "address" } + ], + "name": "repay", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "asset", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" }, + { + "internalType": "uint256", + "name": "interestRateMode", + "type": "uint256" + } + ], + "name": "repayWithATokens", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "asset", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" }, + { + "internalType": "uint256", + "name": "interestRateMode", + "type": "uint256" + }, + { "internalType": "address", "name": "onBehalfOf", "type": "address" }, + { "internalType": "uint256", "name": "deadline", "type": "uint256" }, + { "internalType": "uint8", "name": "permitV", "type": "uint8" }, + { "internalType": "bytes32", "name": "permitR", "type": "bytes32" }, + { "internalType": "bytes32", "name": "permitS", "type": "bytes32" } + ], + "name": "repayWithPermit", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "token", "type": "address" }, + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "rescueTokens", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "asset", "type": "address" } + ], + "name": "resetIsolationModeTotalDebt", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "asset", "type": "address" }, + { + "components": [ + { "internalType": "uint256", "name": "data", "type": "uint256" } + ], + "internalType": "struct DataTypes.ReserveConfigurationMap", + "name": "configuration", + "type": "tuple" + } + ], + "name": "setConfiguration", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "asset", "type": "address" }, + { + "internalType": "address", + "name": "rateStrategyAddress", + "type": "address" + } + ], + "name": "setReserveInterestRateStrategyAddress", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint8", "name": "categoryId", "type": "uint8" } + ], + "name": "setUserEMode", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "asset", "type": "address" }, + { "internalType": "bool", "name": "useAsCollateral", "type": "bool" } + ], + "name": "setUserUseReserveAsCollateral", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "asset", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" }, + { "internalType": "address", "name": "onBehalfOf", "type": "address" }, + { "internalType": "uint16", "name": "referralCode", "type": "uint16" } + ], + "name": "supply", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "asset", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" }, + { "internalType": "address", "name": "onBehalfOf", "type": "address" }, + { "internalType": "uint16", "name": "referralCode", "type": "uint16" }, + { "internalType": "uint256", "name": "deadline", "type": "uint256" }, + { "internalType": "uint8", "name": "permitV", "type": "uint8" }, + { "internalType": "bytes32", "name": "permitR", "type": "bytes32" }, + { "internalType": "bytes32", "name": "permitS", "type": "bytes32" } + ], + "name": "supplyWithPermit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "asset", "type": "address" }, + { + "internalType": "uint256", + "name": "interestRateMode", + "type": "uint256" + } + ], + "name": "swapBorrowRateMode", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "protocolFee", "type": "uint256" } + ], + "name": "updateBridgeProtocolFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint128", + "name": "flashLoanPremiumTotal", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "flashLoanPremiumToProtocol", + "type": "uint128" + } + ], + "name": "updateFlashloanPremiums", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "asset", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" }, + { "internalType": "address", "name": "to", "type": "address" } + ], + "name": "withdraw", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/subgraph/networks.json b/subgraph/networks.json index af87059..8005f24 100644 --- a/subgraph/networks.json +++ b/subgraph/networks.json @@ -25,6 +25,9 @@ "SocialRecovery": { "address": "0x9eF467CAA8291c6DAdD08EA458D763a8258B347e", "startBlock": 24799912 + }, + "zeroUsdtPool": { + "address": "0x4d9429246EA989C9CeE203B43F6d1C7D83e3B8F8" } } } diff --git a/subgraph/src/zero-usdt-pool.ts b/subgraph/src/zero-usdt-pool.ts new file mode 100644 index 0000000..0e354b7 --- /dev/null +++ b/subgraph/src/zero-usdt-pool.ts @@ -0,0 +1,38 @@ +import { Supply, Withdraw } from '../generated/schema'; +import { + Supply as SupplyEvent, + Withdraw as WithdrawEvent, +} from '../generated/zeroUsdtPool/zeroUsdtPool'; + +export function handleSupply(event: SupplyEvent): void { + let entity = new Supply( + event.transaction.hash.concatI32(event.logIndex.toI32()), + ); + entity.reserve = event.params.reserve; + entity.user = event.params.user; + entity.onBehalfOf = event.params.onBehalfOf; + entity.amount = event.params.amount; + entity.referralCode = event.params.referralCode; + + entity.blockNumber = event.block.number; + entity.blockTimestamp = event.block.timestamp; + entity.transactionHash = event.transaction.hash; + + entity.save(); +} + +export function handleWithdraw(event: WithdrawEvent): void { + let entity = new Withdraw( + event.transaction.hash.concatI32(event.logIndex.toI32()), + ); + entity.reserve = event.params.reserve; + entity.user = event.params.user; + entity.to = event.params.to; + entity.amount = event.params.amount; + + entity.blockNumber = event.block.number; + entity.blockTimestamp = event.block.timestamp; + entity.transactionHash = event.transaction.hash; + + entity.save(); +} diff --git a/subgraph/subgraph.yaml b/subgraph/subgraph.yaml index 71dca35..0bf1a11 100644 --- a/subgraph/subgraph.yaml +++ b/subgraph/subgraph.yaml @@ -304,6 +304,32 @@ dataSources: - event: ClaimRewards(indexed address,indexed address,uint256) handler: handleClaimRewards file: ./src/sync-staking.ts + - kind: ethereum + name: zeroUsdtPool + network: zksync-era + source: + address: '0x4d9429246EA989C9CeE203B43F6d1C7D83e3B8F8' + abi: zeroUsdtPool + startBlock: 32062974 + mapping: + kind: ethereum/events + apiVersion: 0.0.7 + language: wasm/assemblyscript + entities: + - Total + - Day + - Week + - Month + - ClaveAccount + abis: + - name: zeroUsdtPool + file: ./abis/zeroUsdtPool.json + eventHandlers: + - event: Supply(indexed address,address,indexed address,uint256,indexed uint16) + handler: handleSupply + - event: Withdraw(indexed address,indexed address,indexed address,uint256) + handler: handleWithdraw + file: ./src/zero-usdt-pool.ts templates: - kind: ethereum name: Account From bc29092f4c2243c485c255ad9d10bdcb7998e5ee Mon Sep 17 00:00:00 2001 From: tahos81 Date: Wed, 12 Jun 2024 16:25:28 +0300 Subject: [PATCH 008/103] format --- subgraph/src/sync-stable-pair.ts | 1 + subgraph/src/sync-staking.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/subgraph/src/sync-stable-pair.ts b/subgraph/src/sync-stable-pair.ts index 244acdf..7d3d022 100644 --- a/subgraph/src/sync-stable-pair.ts +++ b/subgraph/src/sync-stable-pair.ts @@ -3,6 +3,7 @@ * Unauthorized copying of this file, via any medium is strictly prohibited * Proprietary and confidential */ + /* eslint-disable @typescript-eslint/consistent-type-imports */ import { Burn as BurnEvent, diff --git a/subgraph/src/sync-staking.ts b/subgraph/src/sync-staking.ts index c80ce42..429a477 100644 --- a/subgraph/src/sync-staking.ts +++ b/subgraph/src/sync-staking.ts @@ -3,6 +3,7 @@ * Unauthorized copying of this file, via any medium is strictly prohibited * Proprietary and confidential */ + /* eslint-disable @typescript-eslint/consistent-type-imports */ import { ClaimRewards as ClaimRewardsEvent } from '../generated/SyncStaking/SyncStaking'; import { ClaveAccount } from '../generated/schema'; From 8efa7412149a75de03f71a8511e331d727ce26b4 Mon Sep 17 00:00:00 2001 From: tahos81 Date: Wed, 12 Jun 2024 16:25:33 +0300 Subject: [PATCH 009/103] zerolend mapper --- subgraph/src/zero-usdt-pool.ts | 82 ++++++++++++++++++++++------------ 1 file changed, 54 insertions(+), 28 deletions(-) diff --git a/subgraph/src/zero-usdt-pool.ts b/subgraph/src/zero-usdt-pool.ts index 0e354b7..04eeb38 100644 --- a/subgraph/src/zero-usdt-pool.ts +++ b/subgraph/src/zero-usdt-pool.ts @@ -1,38 +1,64 @@ -import { Supply, Withdraw } from '../generated/schema'; +/* eslint-disable @typescript-eslint/consistent-type-imports */ +import { ClaveAccount } from '../generated/schema'; import { Supply as SupplyEvent, Withdraw as WithdrawEvent, } from '../generated/zeroUsdtPool/zeroUsdtPool'; +import { + getOrCreateDay, + getOrCreateMonth, + getOrCreateWeek, + getTotal, +} from './helpers'; export function handleSupply(event: SupplyEvent): void { - let entity = new Supply( - event.transaction.hash.concatI32(event.logIndex.toI32()), - ); - entity.reserve = event.params.reserve; - entity.user = event.params.user; - entity.onBehalfOf = event.params.onBehalfOf; - entity.amount = event.params.amount; - entity.referralCode = event.params.referralCode; - - entity.blockNumber = event.block.number; - entity.blockTimestamp = event.block.timestamp; - entity.transactionHash = event.transaction.hash; - - entity.save(); + const account = ClaveAccount.load(event.params.onBehalfOf); + if (!account) { + return; + } + + const amount = event.params.amount; + + const day = getOrCreateDay(event.block.timestamp); + const week = getOrCreateWeek(event.block.timestamp); + const month = getOrCreateMonth(event.block.timestamp); + const total = getTotal(); + + day.investIn = day.investIn.plus(amount); + week.investIn = week.investIn.plus(amount); + month.investIn = month.investIn.plus(amount); + total.invested = total.invested.plus(amount); + account.invested = account.invested.plus(amount); + + day.save(); + week.save(); + month.save(); + total.save(); + account.save(); } export function handleWithdraw(event: WithdrawEvent): void { - let entity = new Withdraw( - event.transaction.hash.concatI32(event.logIndex.toI32()), - ); - entity.reserve = event.params.reserve; - entity.user = event.params.user; - entity.to = event.params.to; - entity.amount = event.params.amount; - - entity.blockNumber = event.block.number; - entity.blockTimestamp = event.block.timestamp; - entity.transactionHash = event.transaction.hash; - - entity.save(); + const account = ClaveAccount.load(event.params.to); + if (!account) { + return; + } + + const amount = event.params.amount; + + const day = getOrCreateDay(event.block.timestamp); + const week = getOrCreateWeek(event.block.timestamp); + const month = getOrCreateMonth(event.block.timestamp); + const total = getTotal(); + + day.investOut = day.investOut.plus(amount); + week.investOut = week.investOut.plus(amount); + month.investOut = month.investOut.plus(amount); + total.invested = total.invested.minus(amount); + account.invested = account.invested.minus(amount); + + day.save(); + week.save(); + month.save(); + total.save(); + account.save(); } From 3d8de5f2a6a0f243e493ca9ca23bcc9a118f5346 Mon Sep 17 00:00:00 2001 From: tahos81 Date: Wed, 12 Jun 2024 16:25:48 +0300 Subject: [PATCH 010/103] husky --- subgraph/src/zero-usdt-pool.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/subgraph/src/zero-usdt-pool.ts b/subgraph/src/zero-usdt-pool.ts index 04eeb38..d24eea4 100644 --- a/subgraph/src/zero-usdt-pool.ts +++ b/subgraph/src/zero-usdt-pool.ts @@ -1,3 +1,9 @@ +/** + * Copyright Clave - All Rights Reserved + * Unauthorized copying of this file, via any medium is strictly prohibited + * Proprietary and confidential + */ + /* eslint-disable @typescript-eslint/consistent-type-imports */ import { ClaveAccount } from '../generated/schema'; import { From 822b7b6529d9f67f3fb00a3c5b63509b4ce86c9c Mon Sep 17 00:00:00 2001 From: tahos81 Date: Sat, 15 Jun 2024 16:39:51 +0300 Subject: [PATCH 011/103] feat: skip some tokens for zerolend --- subgraph/src/zero-usdt-pool.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/subgraph/src/zero-usdt-pool.ts b/subgraph/src/zero-usdt-pool.ts index d24eea4..214cb6e 100644 --- a/subgraph/src/zero-usdt-pool.ts +++ b/subgraph/src/zero-usdt-pool.ts @@ -5,6 +5,8 @@ */ /* eslint-disable @typescript-eslint/consistent-type-imports */ +import { log } from '@graphprotocol/graph-ts'; + import { ClaveAccount } from '../generated/schema'; import { Supply as SupplyEvent, @@ -17,12 +19,27 @@ import { getTotal, } from './helpers'; +const tokens = [ + '0x3355df6D4c9C3035724Fd0e3914dE96A5a83aaf4', + '0x493257fD37EDB34451f62EDf8D2a0C418852bA4C', + '0x4B9eb6c0b6ea15176BBF62841C6B2A8a398cb656', + '0x1d17cbcf0d6d143135ae902365d2e5e2a16538d4', +]; + export function handleSupply(event: SupplyEvent): void { const account = ClaveAccount.load(event.params.onBehalfOf); if (!account) { return; } + // skip if event.params.reserve not in tokens + if (tokens.indexOf(event.params.reserve.toHexString()) === -1) { + log.info('Skipped: {}', [event.params.reserve.toHexString()]); + return; + } + + log.info('Supply: {}', [event.params.reserve.toHexString()]); + const amount = event.params.amount; const day = getOrCreateDay(event.block.timestamp); From 6f22c8c08d604266259170c942324d6cd7647f4e Mon Sep 17 00:00:00 2001 From: tahos81 Date: Sat, 15 Jun 2024 16:40:03 +0300 Subject: [PATCH 012/103] start indexing earlier for earn --- subgraph/subgraph.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/subgraph/subgraph.yaml b/subgraph/subgraph.yaml index 0bf1a11..02fc42f 100644 --- a/subgraph/subgraph.yaml +++ b/subgraph/subgraph.yaml @@ -260,7 +260,7 @@ dataSources: source: address: '0x12e7A9423d9128287E63017eE6d1f20e1C237f15' abi: SyncStablePool - startBlock: 34062974 + startBlock: 24799912 mapping: kind: ethereum/events apiVersion: 0.0.7 @@ -286,7 +286,7 @@ dataSources: source: address: '0x2B9a7d5cD64E5c1446b32e034e75A5C93B0C8bB5' abi: SyncStaking - startBlock: 32062974 + startBlock: 24799912 mapping: kind: ethereum/events apiVersion: 0.0.7 @@ -310,7 +310,7 @@ dataSources: source: address: '0x4d9429246EA989C9CeE203B43F6d1C7D83e3B8F8' abi: zeroUsdtPool - startBlock: 32062974 + startBlock: 24799912 mapping: kind: ethereum/events apiVersion: 0.0.7 From f2ea64ef82f3efe9173d4c872f11795abb16ba9f Mon Sep 17 00:00:00 2001 From: tahos81 Date: Tue, 18 Jun 2024 13:49:46 +0300 Subject: [PATCH 013/103] fix test utils --- subgraph/tests/new-account-utils.ts | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/subgraph/tests/new-account-utils.ts b/subgraph/tests/new-account-utils.ts index fd85124..14168ef 100644 --- a/subgraph/tests/new-account-utils.ts +++ b/subgraph/tests/new-account-utils.ts @@ -9,7 +9,6 @@ import { Address, ethereum } from '@graphprotocol/graph-ts'; import { newMockEvent } from 'matchstick-as'; import { NewClaveAccount } from '../generated/AccountFactory/AccountFactory'; -import { Transfer } from '../generated/erc20/ERC20'; export function createNewClaveAccountEvent( accountAddress: Address, @@ -26,22 +25,3 @@ export function createNewClaveAccountEvent( return newClaveAccountEvent; } - -export function createNewTransferEvent( - from: Address, - to: Address, - value: ethereum.Value, -): Transfer { - const newTransferEvent = changetype(newMockEvent()); - newTransferEvent.parameters = []; - - newTransferEvent.parameters.push( - new ethereum.EventParam('from', ethereum.Value.fromAddress(from)), - ); - newTransferEvent.parameters.push( - new ethereum.EventParam('to', ethereum.Value.fromAddress(to)), - ); - newTransferEvent.parameters.push(new ethereum.EventParam('value', value)); - - return newTransferEvent; -} From 9b9dd2d156dee57c8093a6d24677fcc64435f71a Mon Sep 17 00:00:00 2001 From: tahos81 Date: Tue, 18 Jun 2024 13:49:52 +0300 Subject: [PATCH 014/103] fix zerolend mapper --- subgraph/src/zero-usdt-pool.ts | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/subgraph/src/zero-usdt-pool.ts b/subgraph/src/zero-usdt-pool.ts index 214cb6e..106765f 100644 --- a/subgraph/src/zero-usdt-pool.ts +++ b/subgraph/src/zero-usdt-pool.ts @@ -20,10 +20,10 @@ import { } from './helpers'; const tokens = [ - '0x3355df6D4c9C3035724Fd0e3914dE96A5a83aaf4', - '0x493257fD37EDB34451f62EDf8D2a0C418852bA4C', - '0x4B9eb6c0b6ea15176BBF62841C6B2A8a398cb656', - '0x1d17cbcf0d6d143135ae902365d2e5e2a16538d4', + '0x3355df6D4c9C3035724Fd0e3914dE96A5a83aaf4'.toLowerCase(), + '0x493257fD37EDB34451f62EDf8D2a0C418852bA4C'.toLowerCase(), + '0x4B9eb6c0b6ea15176BBF62841C6B2A8a398cb656'.toLowerCase(), + '0x1d17cbcf0d6d143135ae902365d2e5e2a16538d4'.toLowerCase(), ]; export function handleSupply(event: SupplyEvent): void { @@ -33,7 +33,9 @@ export function handleSupply(event: SupplyEvent): void { } // skip if event.params.reserve not in tokens - if (tokens.indexOf(event.params.reserve.toHexString()) === -1) { + if ( + tokens.indexOf(event.params.reserve.toHexString().toLowerCase()) === -1 + ) { log.info('Skipped: {}', [event.params.reserve.toHexString()]); return; } @@ -66,6 +68,16 @@ export function handleWithdraw(event: WithdrawEvent): void { return; } + // skip if event.params.reserve not in tokens + if ( + tokens.indexOf(event.params.reserve.toHexString().toLowerCase()) === -1 + ) { + log.info('Skipped: {}', [event.params.reserve.toHexString()]); + return; + } + + log.info('Supply: {}', [event.params.reserve.toHexString()]); + const amount = event.params.amount; const day = getOrCreateDay(event.block.timestamp); From a1821d4edab2a2248a68ec21da4dcc800f5c5a87 Mon Sep 17 00:00:00 2001 From: tahos81 Date: Tue, 18 Jun 2024 14:12:29 +0300 Subject: [PATCH 015/103] feat: earn overhaul to schema --- subgraph/schema.graphql | 83 ++++++++++++++++++++++++++++------------- 1 file changed, 57 insertions(+), 26 deletions(-) diff --git a/subgraph/schema.graphql b/subgraph/schema.graphql index 669aeac..f3d0b8d 100644 --- a/subgraph/schema.graphql +++ b/subgraph/schema.graphql @@ -6,10 +6,6 @@ type Total @entity { transactions: Int! backedUp: Int! gasSponsored: BigInt! - invested: BigInt! - investedEth: BigInt! - realizedGain: BigInt! - realizedGainEth: BigInt! } type Day @entity { @@ -20,12 +16,7 @@ type Day @entity { activeAccounts: Int! transactions: Int! gasSponsored: BigInt! - investIn: BigInt! - investInEth: BigInt! - investOut: BigInt! - investOutEth: BigInt! - realizedGain: BigInt! - realizedGainEth: BigInt! + investFlow: [DailyEarnFlow!]! @derivedFrom(field: "day") swappedTo: [DailySwappedTo!]! @derivedFrom(field: "day") } @@ -37,12 +28,7 @@ type Week @entity { activeAccounts: Int! transactions: Int! gasSponsored: BigInt! - investIn: BigInt! - investInEth: BigInt! - investOut: BigInt! - investOutEth: BigInt! - realizedGain: BigInt! - realizedGainEth: BigInt! + investFlow: [WeeklyEarnFlow!]! @derivedFrom(field: "week") swappedTo: [WeeklySwappedTo!]! @derivedFrom(field: "week") } @@ -54,15 +40,63 @@ type Month @entity { activeAccounts: Int! transactions: Int! gasSponsored: BigInt! - investIn: BigInt! - investInEth: BigInt! - investOut: BigInt! - investOutEth: BigInt! - realizedGain: BigInt! - realizedGainEth: BigInt! + investFlow: [MonthlyEarnFlow!]! @derivedFrom(field: "month") swappedTo: [MonthlySwappedTo!]! @derivedFrom(field: "month") } +enum EarnProtocol { + Koi + SyncSwap + ZeroLend + Uniswap + Clave +} + +type EarnPosition @entity { + "account.id.concat(pool).concat(token)" + id: Bytes! + account: ClaveAccount! + pool: Bytes! + token: Bytes! + protocol: EarnProtocol! + invested: BigInt! + compoundGain: BigInt! + normalGain: BigInt! +} + +type DailyEarnFlow @entity { + "day.id.concat(erc20).concat(protocol)" + id: Bytes! + day: Day! + erc20: Bytes! + protocol: EarnProtocol! + amountIn: BigInt! + amountOut: BigInt! + claimedGain: BigInt! +} + +type WeeklyEarnFlow @entity { + "week.id.concat(erc20).concat(protocol)" + id: Bytes! + week: Week! + erc20: Bytes! + protocol: EarnProtocol! + amountIn: BigInt! + amountOut: BigInt! + claimedGain: BigInt! +} + +type MonthlyEarnFlow @entity { + "month.id.concat(erc20).concat(protocol)" + id: Bytes! + month: Month! + erc20: Bytes! + protocol: EarnProtocol! + amountIn: BigInt! + amountOut: BigInt! + claimedGain: BigInt! +} + type DailySwappedTo @entity { "day.id.concat(erc20)" id: Bytes! @@ -119,10 +153,7 @@ type ClaveAccount @entity { txCount: Int! "account implementation address" implementation: Bytes - invested: BigInt! - investedEth: BigInt! - realizedGain: BigInt! - realizedGainEth: BigInt! + earnPositions: [EarnPosition!]! @derivedFrom(field: "account") transactions: [ClaveTransaction!]! @derivedFrom(field: "sender") inAppSwaps: [InAppSwap!]! @derivedFrom(field: "account") activeDays: [DayAccount!]! @derivedFrom(field: "account") From cf6cd6ecd635e3e6fb04dbd3b736a5a5a79b98f0 Mon Sep 17 00:00:00 2001 From: tahos81 Date: Tue, 18 Jun 2024 17:29:01 +0300 Subject: [PATCH 016/103] delete uniswap from protocols --- subgraph/schema.graphql | 1 - 1 file changed, 1 deletion(-) diff --git a/subgraph/schema.graphql b/subgraph/schema.graphql index f3d0b8d..68cf6b3 100644 --- a/subgraph/schema.graphql +++ b/subgraph/schema.graphql @@ -48,7 +48,6 @@ enum EarnProtocol { Koi SyncSwap ZeroLend - Uniswap Clave } From 6c9048efb84b8abe740191e81e9fb79e592812f7 Mon Sep 17 00:00:00 2001 From: tahos81 Date: Tue, 18 Jun 2024 17:29:05 +0300 Subject: [PATCH 017/103] Update helpers.ts --- subgraph/src/helpers.ts | 126 ++++++++++++++++++++++++++++++++-------- 1 file changed, 103 insertions(+), 23 deletions(-) diff --git a/subgraph/src/helpers.ts b/subgraph/src/helpers.ts index 878cdb6..cf76b9d 100644 --- a/subgraph/src/helpers.ts +++ b/subgraph/src/helpers.ts @@ -9,18 +9,22 @@ /* eslint-disable @typescript-eslint/consistent-type-imports */ /* eslint-disable prefer-const */ -import { Bytes } from '@graphprotocol/graph-ts'; +import { ByteArray, Bytes } from '@graphprotocol/graph-ts'; import { BigInt } from '@graphprotocol/graph-ts'; import { ClaveAccount, + DailyEarnFlow, Day, DayAccount, + EarnPosition, Month, MonthAccount, + MonthlyEarnFlow, Total, Week, WeekAccount, + WeeklyEarnFlow, } from '../generated/schema'; export const ZERO = BigInt.fromI32(0); @@ -47,12 +51,6 @@ export function getOrCreateDay(timestamp: BigInt): Day { day.deployedAccounts = 0; day.activeAccounts = 0; day.transactions = 0; - day.investIn = ZERO; - day.investInEth = ZERO; - day.investOut = ZERO; - day.investOutEth = ZERO; - day.realizedGain = ZERO; - day.realizedGainEth = ZERO; day.gasSponsored = ZERO; return day; @@ -94,12 +92,6 @@ export function getOrCreateWeek(timestamp: BigInt): Week { week.deployedAccounts = 0; week.activeAccounts = 0; week.transactions = 0; - week.investIn = ZERO; - week.investInEth = ZERO; - week.investOut = ZERO; - week.investOutEth = ZERO; - week.realizedGain = ZERO; - week.realizedGainEth = ZERO; week.gasSponsored = ZERO; return week; @@ -141,12 +133,6 @@ export function getOrCreateMonth(timestamp: BigInt): Month { month.deployedAccounts = 0; month.activeAccounts = 0; month.transactions = 0; - month.investIn = ZERO; - month.investInEth = ZERO; - month.investOut = ZERO; - month.investOutEth = ZERO; - month.realizedGain = ZERO; - month.realizedGainEth = ZERO; month.gasSponsored = ZERO; return month; @@ -185,11 +171,105 @@ export function getTotal(): Total { total.deployedAccounts = 0; total.transactions = 0; total.backedUp = 0; - total.invested = ZERO; - total.investedEth = ZERO; - total.realizedGain = ZERO; - total.realizedGainEth = ZERO; total.gasSponsored = ZERO; return total; } + +export function getOrCreateEarnPosition( + account: ClaveAccount, + pool: ByteArray, + token: ByteArray, + protocol: string, +): EarnPosition { + let earnPositionId = account.id.concat(pool).concat(token); + let earnPosition = EarnPosition.load(earnPositionId); + + if (earnPosition !== null) { + return earnPosition; + } + + earnPosition = new EarnPosition(earnPositionId); + earnPosition.account = account.id; + earnPosition.pool = pool; + earnPosition.token = token; + earnPosition.protocol = protocol; + earnPosition.invested = ZERO; + earnPosition.compoundGain = ZERO; + earnPosition.normalGain = ZERO; + + return earnPosition; +} + +export function getOrCreateDailyEarnFlow( + day: Day, + token: ByteArray, + protocol: string, +): DailyEarnFlow { + let dailyEarnFlowId = day.id.concat(token).concat(Bytes.fromUTF8(protocol)); + let dailyEarnFlow = DailyEarnFlow.load(dailyEarnFlowId); + + if (dailyEarnFlow !== null) { + return dailyEarnFlow; + } + + dailyEarnFlow = new DailyEarnFlow(dailyEarnFlowId); + dailyEarnFlow.day = day.id; + dailyEarnFlow.erc20 = token; + dailyEarnFlow.protocol = protocol; + dailyEarnFlow.amountIn = ZERO; + dailyEarnFlow.amountOut = ZERO; + dailyEarnFlow.claimedGain = ZERO; + + return dailyEarnFlow; +} + +export function getOrCreateWeeklyEarnFlow( + week: Week, + token: ByteArray, + protocol: string, +): WeeklyEarnFlow { + let weeklyEarnFlowId = week.id + .concat(token) + .concat(Bytes.fromUTF8(protocol)); + let weeklyEarnFlow = WeeklyEarnFlow.load(weeklyEarnFlowId); + + if (weeklyEarnFlow !== null) { + return weeklyEarnFlow; + } + + weeklyEarnFlow = new WeeklyEarnFlow(weeklyEarnFlowId); + weeklyEarnFlow.week = week.id; + weeklyEarnFlow.erc20 = token; + weeklyEarnFlow.protocol = protocol; + weeklyEarnFlow.amountIn = ZERO; + weeklyEarnFlow.amountOut = ZERO; + weeklyEarnFlow.claimedGain = ZERO; + + return weeklyEarnFlow; +} + +export function getOrCreateMonthlyEarnFlow( + month: Month, + token: ByteArray, + protocol: string, +): MonthlyEarnFlow { + let monthlyEarnFlowId = month.id + .concat(token) + .concat(Bytes.fromUTF8(protocol)); + let monthlyEarnFlow = MonthlyEarnFlow.load(monthlyEarnFlowId); + + if (monthlyEarnFlow !== null) { + return monthlyEarnFlow; + } + + monthlyEarnFlow = new MonthlyEarnFlow(monthlyEarnFlowId); + monthlyEarnFlow.month = month.id; + monthlyEarnFlow.erc20 = token; + monthlyEarnFlow.protocol = protocol; + monthlyEarnFlow.amountIn = ZERO; + monthlyEarnFlow.amountOut = ZERO; + monthlyEarnFlow.claimedGain = ZERO; + + return monthlyEarnFlow; +} From b61efd4e8276e9ff16d0dad5258fc5594b8a25ae Mon Sep 17 00:00:00 2001 From: tahos81 Date: Tue, 18 Jun 2024 17:29:24 +0300 Subject: [PATCH 018/103] fix zerolend mapper --- subgraph/src/zero-usdt-pool.ts | 117 ++++++++++++++++++--------------- 1 file changed, 65 insertions(+), 52 deletions(-) diff --git a/subgraph/src/zero-usdt-pool.ts b/subgraph/src/zero-usdt-pool.ts index 106765f..a077237 100644 --- a/subgraph/src/zero-usdt-pool.ts +++ b/subgraph/src/zero-usdt-pool.ts @@ -5,26 +5,23 @@ */ /* eslint-disable @typescript-eslint/consistent-type-imports */ -import { log } from '@graphprotocol/graph-ts'; - import { ClaveAccount } from '../generated/schema'; import { Supply as SupplyEvent, Withdraw as WithdrawEvent, } from '../generated/zeroUsdtPool/zeroUsdtPool'; import { + ZERO, + getOrCreateDailyEarnFlow, getOrCreateDay, + getOrCreateEarnPosition, getOrCreateMonth, + getOrCreateMonthlyEarnFlow, getOrCreateWeek, - getTotal, + getOrCreateWeeklyEarnFlow, } from './helpers'; -const tokens = [ - '0x3355df6D4c9C3035724Fd0e3914dE96A5a83aaf4'.toLowerCase(), - '0x493257fD37EDB34451f62EDf8D2a0C418852bA4C'.toLowerCase(), - '0x4B9eb6c0b6ea15176BBF62841C6B2A8a398cb656'.toLowerCase(), - '0x1d17cbcf0d6d143135ae902365d2e5e2a16538d4'.toLowerCase(), -]; +const protocol = 'ZeroLend'; export function handleSupply(event: SupplyEvent): void { const account = ClaveAccount.load(event.params.onBehalfOf); @@ -32,34 +29,33 @@ export function handleSupply(event: SupplyEvent): void { return; } - // skip if event.params.reserve not in tokens - if ( - tokens.indexOf(event.params.reserve.toHexString().toLowerCase()) === -1 - ) { - log.info('Skipped: {}', [event.params.reserve.toHexString()]); - return; - } - - log.info('Supply: {}', [event.params.reserve.toHexString()]); - + const pool = event.address; + const token = event.params.reserve; const amount = event.params.amount; const day = getOrCreateDay(event.block.timestamp); const week = getOrCreateWeek(event.block.timestamp); const month = getOrCreateMonth(event.block.timestamp); - const total = getTotal(); - - day.investIn = day.investIn.plus(amount); - week.investIn = week.investIn.plus(amount); - month.investIn = month.investIn.plus(amount); - total.invested = total.invested.plus(amount); - account.invested = account.invested.plus(amount); - day.save(); - week.save(); - month.save(); - total.save(); - account.save(); + const dailyEarnFlow = getOrCreateDailyEarnFlow(day, token, protocol); + const weeklyEarnFlow = getOrCreateWeeklyEarnFlow(week, token, protocol); + const monthlyEarnFlow = getOrCreateMonthlyEarnFlow(month, token, protocol); + const earnPosition = getOrCreateEarnPosition( + account, + pool, + token, + protocol, + ); + + dailyEarnFlow.amountIn = dailyEarnFlow.amountIn.plus(amount); + weeklyEarnFlow.amountIn = weeklyEarnFlow.amountIn.plus(amount); + monthlyEarnFlow.amountIn = monthlyEarnFlow.amountIn.plus(amount); + earnPosition.invested = earnPosition.invested.plus(amount); + + dailyEarnFlow.save(); + weeklyEarnFlow.save(); + monthlyEarnFlow.save(); + earnPosition.save(); } export function handleWithdraw(event: WithdrawEvent): void { @@ -68,32 +64,49 @@ export function handleWithdraw(event: WithdrawEvent): void { return; } - // skip if event.params.reserve not in tokens - if ( - tokens.indexOf(event.params.reserve.toHexString().toLowerCase()) === -1 - ) { - log.info('Skipped: {}', [event.params.reserve.toHexString()]); - return; - } - - log.info('Supply: {}', [event.params.reserve.toHexString()]); - + const pool = event.address; + const token = event.params.reserve; const amount = event.params.amount; const day = getOrCreateDay(event.block.timestamp); const week = getOrCreateWeek(event.block.timestamp); const month = getOrCreateMonth(event.block.timestamp); - const total = getTotal(); - day.investOut = day.investOut.plus(amount); - week.investOut = week.investOut.plus(amount); - month.investOut = month.investOut.plus(amount); - total.invested = total.invested.minus(amount); - account.invested = account.invested.minus(amount); + const dailyEarnFlow = getOrCreateDailyEarnFlow(day, token, protocol); + const weeklyEarnFlow = getOrCreateWeeklyEarnFlow(week, token, protocol); + const monthlyEarnFlow = getOrCreateMonthlyEarnFlow(month, token, protocol); + const earnPosition = getOrCreateEarnPosition( + account, + pool, + token, + protocol, + ); + + const invested = earnPosition.invested; + + if (amount.gt(invested)) { + const compoundGain = amount.minus(invested); + dailyEarnFlow.amountOut = dailyEarnFlow.amountOut.plus(invested); + dailyEarnFlow.claimedGain = + dailyEarnFlow.claimedGain.plus(compoundGain); + weeklyEarnFlow.amountOut = weeklyEarnFlow.amountOut.plus(invested); + weeklyEarnFlow.claimedGain = + weeklyEarnFlow.claimedGain.plus(compoundGain); + monthlyEarnFlow.amountOut = monthlyEarnFlow.amountOut.plus(invested); + monthlyEarnFlow.claimedGain = + monthlyEarnFlow.claimedGain.plus(compoundGain); + earnPosition.invested = ZERO; + earnPosition.compoundGain = + earnPosition.compoundGain.plus(compoundGain); + } else { + dailyEarnFlow.amountOut = dailyEarnFlow.amountOut.plus(amount); + weeklyEarnFlow.amountOut = weeklyEarnFlow.amountOut.plus(amount); + monthlyEarnFlow.amountOut = monthlyEarnFlow.amountOut.plus(amount); + earnPosition.invested = invested.minus(amount); + } - day.save(); - week.save(); - month.save(); - total.save(); - account.save(); + dailyEarnFlow.save(); + weeklyEarnFlow.save(); + monthlyEarnFlow.save(); + earnPosition.save(); } From fb3f134eb16634443865163bd634fba0e3834e6f Mon Sep 17 00:00:00 2001 From: tahos81 Date: Tue, 18 Jun 2024 17:29:33 +0300 Subject: [PATCH 019/103] fix sync-staking mapper --- subgraph/src/sync-staking.ts | 37 ++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/subgraph/src/sync-staking.ts b/subgraph/src/sync-staking.ts index 429a477..09abbab 100644 --- a/subgraph/src/sync-staking.ts +++ b/subgraph/src/sync-staking.ts @@ -8,34 +8,47 @@ import { ClaimRewards as ClaimRewardsEvent } from '../generated/SyncStaking/SyncStaking'; import { ClaveAccount } from '../generated/schema'; import { + getOrCreateDailyEarnFlow, getOrCreateDay, + getOrCreateEarnPosition, getOrCreateMonth, + getOrCreateMonthlyEarnFlow, getOrCreateWeek, - getTotal, + getOrCreateWeeklyEarnFlow, } from './helpers'; +const protocol = 'SyncSwap'; + export function handleClaimRewards(event: ClaimRewardsEvent): void { const account = ClaveAccount.load(event.params.from); if (!account) { return; } + const pool = event.address; + const token = event.params.reward; const amount = event.params.amount; const day = getOrCreateDay(event.block.timestamp); const week = getOrCreateWeek(event.block.timestamp); const month = getOrCreateMonth(event.block.timestamp); - const total = getTotal(); + const dailyEarnFlow = getOrCreateDailyEarnFlow(day, token, protocol); + const weeklyEarnFlow = getOrCreateWeeklyEarnFlow(week, token, protocol); + const monthlyEarnFlow = getOrCreateMonthlyEarnFlow(month, token, protocol); + const earnPosition = getOrCreateEarnPosition( + account, + pool, + token, + protocol, + ); - day.realizedGainEth = day.realizedGainEth.plus(amount); - week.realizedGainEth = week.realizedGainEth.plus(amount); - month.realizedGainEth = month.realizedGainEth.plus(amount); - total.realizedGainEth = total.realizedGainEth.plus(amount); - account.realizedGainEth = account.realizedGainEth.plus(amount); + dailyEarnFlow.claimedGain = dailyEarnFlow.claimedGain.plus(amount); + weeklyEarnFlow.claimedGain = weeklyEarnFlow.claimedGain.plus(amount); + monthlyEarnFlow.claimedGain = monthlyEarnFlow.claimedGain.plus(amount); + earnPosition.normalGain = earnPosition.normalGain.plus(amount); - day.save(); - week.save(); - month.save(); - total.save(); - account.save(); + dailyEarnFlow.save(); + weeklyEarnFlow.save(); + monthlyEarnFlow.save(); + earnPosition.save(); } From c0a44f738f4bd7a369f1347df6664e0cf8e9c22c Mon Sep 17 00:00:00 2001 From: tahos81 Date: Tue, 18 Jun 2024 17:29:38 +0300 Subject: [PATCH 020/103] fix sync pair mapper --- subgraph/src/sync-stable-pair.ts | 113 +++++++++++++++++++++++-------- 1 file changed, 84 insertions(+), 29 deletions(-) diff --git a/subgraph/src/sync-stable-pair.ts b/subgraph/src/sync-stable-pair.ts index 7d3d022..dda5369 100644 --- a/subgraph/src/sync-stable-pair.ts +++ b/subgraph/src/sync-stable-pair.ts @@ -5,42 +5,66 @@ */ /* eslint-disable @typescript-eslint/consistent-type-imports */ +import { Address } from '@graphprotocol/graph-ts'; + import { Burn as BurnEvent, Mint as MintEvent, } from '../generated/SyncEthWstethPool/SyncStablePool'; import { ClaveAccount } from '../generated/schema'; import { + ZERO, + getOrCreateDailyEarnFlow, getOrCreateDay, + getOrCreateEarnPosition, getOrCreateMonth, + getOrCreateMonthlyEarnFlow, getOrCreateWeek, - getTotal, + getOrCreateWeeklyEarnFlow, } from './helpers'; +const protocol = 'SyncSwap'; +const token = Address.fromHexString( + '0x000000000000000000000000000000000000800A', +); + export function handleMint(event: MintEvent): void { const account = ClaveAccount.load(event.transaction.from); if (!account) { return; } - const amount = event.params.amount1.plus(event.params.amount0); + const pool = event.address; + const amount0 = event.params.amount0; + const amount1 = event.params.amount1; + + if (amount1.gt(ZERO)) { + return; + } const day = getOrCreateDay(event.block.timestamp); const week = getOrCreateWeek(event.block.timestamp); const month = getOrCreateMonth(event.block.timestamp); - const total = getTotal(); - - day.investInEth = day.investInEth.plus(amount); - week.investInEth = week.investInEth.plus(amount); - month.investInEth = month.investInEth.plus(amount); - total.investedEth = total.investedEth.plus(amount); - account.investedEth = account.investedEth.plus(amount); - - day.save(); - week.save(); - month.save(); - total.save(); - account.save(); + + const dailyEarnFlow = getOrCreateDailyEarnFlow(day, token, protocol); + const weeklyEarnFlow = getOrCreateWeeklyEarnFlow(week, token, protocol); + const monthlyEarnFlow = getOrCreateMonthlyEarnFlow(month, token, protocol); + const earnPosition = getOrCreateEarnPosition( + account, + pool, + token, + protocol, + ); + + dailyEarnFlow.amountIn = dailyEarnFlow.amountIn.plus(amount0); + weeklyEarnFlow.amountIn = weeklyEarnFlow.amountIn.plus(amount0); + monthlyEarnFlow.amountIn = monthlyEarnFlow.amountIn.plus(amount0); + earnPosition.invested = earnPosition.invested.plus(amount0); + + dailyEarnFlow.save(); + weeklyEarnFlow.save(); + monthlyEarnFlow.save(); + earnPosition.save(); } export function handleBurn(event: BurnEvent): void { @@ -49,22 +73,53 @@ export function handleBurn(event: BurnEvent): void { return; } - const amount = event.params.amount1.plus(event.params.amount0); + const pool = event.address; + const amount0 = event.params.amount0; + const amount1 = event.params.amount1; + + if (amount1.gt(ZERO)) { + return; + } const day = getOrCreateDay(event.block.timestamp); const week = getOrCreateWeek(event.block.timestamp); const month = getOrCreateMonth(event.block.timestamp); - const total = getTotal(); - - day.investOutEth = day.investOutEth.plus(amount); - week.investOutEth = week.investOutEth.plus(amount); - month.investOutEth = month.investOutEth.plus(amount); - total.investedEth = total.investedEth.minus(amount); - account.investedEth = account.investedEth.minus(amount); - - day.save(); - week.save(); - month.save(); - total.save(); - account.save(); + + const dailyEarnFlow = getOrCreateDailyEarnFlow(day, token, protocol); + const weeklyEarnFlow = getOrCreateWeeklyEarnFlow(week, token, protocol); + const monthlyEarnFlow = getOrCreateMonthlyEarnFlow(month, token, protocol); + const earnPosition = getOrCreateEarnPosition( + account, + pool, + token, + protocol, + ); + + const invested = earnPosition.invested; + + if (amount0.gt(invested)) { + const compoundGain = amount0.minus(invested); + dailyEarnFlow.amountOut = dailyEarnFlow.amountOut.plus(invested); + dailyEarnFlow.claimedGain = + dailyEarnFlow.claimedGain.plus(compoundGain); + weeklyEarnFlow.amountOut = weeklyEarnFlow.amountOut.plus(invested); + weeklyEarnFlow.claimedGain = + weeklyEarnFlow.claimedGain.plus(compoundGain); + monthlyEarnFlow.amountOut = monthlyEarnFlow.amountOut.plus(invested); + monthlyEarnFlow.claimedGain = + monthlyEarnFlow.claimedGain.plus(compoundGain); + earnPosition.invested = ZERO; + earnPosition.compoundGain = + earnPosition.compoundGain.plus(compoundGain); + } else { + dailyEarnFlow.amountOut = dailyEarnFlow.amountOut.plus(amount0); + weeklyEarnFlow.amountOut = weeklyEarnFlow.amountOut.plus(amount0); + monthlyEarnFlow.amountOut = monthlyEarnFlow.amountOut.plus(amount0); + earnPosition.invested = earnPosition.invested.minus(amount0); + } + + dailyEarnFlow.save(); + weeklyEarnFlow.save(); + monthlyEarnFlow.save(); + earnPosition.save(); } From 4dd1d5a5821b9b8517a9b529f2051e58208122af Mon Sep 17 00:00:00 2001 From: tahos81 Date: Tue, 18 Jun 2024 17:29:51 +0300 Subject: [PATCH 021/103] fix koi pair mapper --- subgraph/src/koi-pair.ts | 109 +++++++++++++++++++++++++-------------- 1 file changed, 69 insertions(+), 40 deletions(-) diff --git a/subgraph/src/koi-pair.ts b/subgraph/src/koi-pair.ts index 7a34727..3bf4a67 100644 --- a/subgraph/src/koi-pair.ts +++ b/subgraph/src/koi-pair.ts @@ -5,6 +5,8 @@ */ /* eslint-disable @typescript-eslint/consistent-type-imports */ +import { Address } from '@graphprotocol/graph-ts'; + import { Burn as BurnEvent, Claim as ClaimEvent, @@ -12,36 +14,52 @@ import { } from '../generated/KoiUsdceUsdt/KoiPair'; import { ClaveAccount } from '../generated/schema'; import { + getOrCreateDailyEarnFlow, getOrCreateDay, + getOrCreateEarnPosition, getOrCreateMonth, + getOrCreateMonthlyEarnFlow, getOrCreateWeek, - getTotal, + getOrCreateWeeklyEarnFlow, } from './helpers'; +const protocol = 'Koi'; +const token = Address.fromHexString( + '0x3355df6D4c9C3035724Fd0e3914dE96A5a83aaf4', +); + export function handleMint(event: MintEvent): void { const account = ClaveAccount.load(event.transaction.from); if (!account) { return; } + const pool = event.address; const amount = event.params.amount1.plus(event.params.amount0); const day = getOrCreateDay(event.block.timestamp); const week = getOrCreateWeek(event.block.timestamp); const month = getOrCreateMonth(event.block.timestamp); - const total = getTotal(); - - day.investIn = day.investIn.plus(amount); - week.investIn = week.investIn.plus(amount); - month.investIn = month.investIn.plus(amount); - total.invested = total.invested.plus(amount); - account.invested = account.invested.plus(amount); - - day.save(); - week.save(); - month.save(); - total.save(); - account.save(); + + const dailyEarnFlow = getOrCreateDailyEarnFlow(day, token, protocol); + const weeklyEarnFlow = getOrCreateWeeklyEarnFlow(week, token, protocol); + const monthlyEarnFlow = getOrCreateMonthlyEarnFlow(month, token, protocol); + const earnPosition = getOrCreateEarnPosition( + account, + pool, + token, + protocol, + ); + + dailyEarnFlow.amountIn = dailyEarnFlow.amountIn.plus(amount); + weeklyEarnFlow.amountIn = weeklyEarnFlow.amountIn.plus(amount); + monthlyEarnFlow.amountIn = monthlyEarnFlow.amountIn.plus(amount); + earnPosition.invested = earnPosition.invested.plus(amount); + + dailyEarnFlow.save(); + weeklyEarnFlow.save(); + monthlyEarnFlow.save(); + earnPosition.save(); } export function handleBurn(event: BurnEvent): void { @@ -50,24 +68,32 @@ export function handleBurn(event: BurnEvent): void { return; } + const pool = event.address; const amount = event.params.amount1.plus(event.params.amount0); const day = getOrCreateDay(event.block.timestamp); const week = getOrCreateWeek(event.block.timestamp); const month = getOrCreateMonth(event.block.timestamp); - const total = getTotal(); - - day.investOut = day.investOut.plus(amount); - week.investOut = week.investOut.plus(amount); - month.investOut = month.investOut.plus(amount); - total.invested = total.invested.minus(amount); - account.invested = account.invested.minus(amount); - - day.save(); - week.save(); - month.save(); - total.save(); - account.save(); + + const dailyEarnFlow = getOrCreateDailyEarnFlow(day, token, protocol); + const weeklyEarnFlow = getOrCreateWeeklyEarnFlow(week, token, protocol); + const monthlyEarnFlow = getOrCreateMonthlyEarnFlow(month, token, protocol); + const earnPosition = getOrCreateEarnPosition( + account, + pool, + token, + protocol, + ); + + dailyEarnFlow.amountOut = dailyEarnFlow.amountOut.plus(amount); + weeklyEarnFlow.amountOut = weeklyEarnFlow.amountOut.plus(amount); + monthlyEarnFlow.amountOut = monthlyEarnFlow.amountOut.plus(amount); + earnPosition.invested = earnPosition.invested.minus(amount); + + dailyEarnFlow.save(); + weeklyEarnFlow.save(); + monthlyEarnFlow.save(); + earnPosition.save(); } export function handleClaim(event: ClaimEvent): void { @@ -76,22 +102,25 @@ export function handleClaim(event: ClaimEvent): void { return; } + const pool = event.address; const amount = event.params.amount1.plus(event.params.amount0); const day = getOrCreateDay(event.block.timestamp); const week = getOrCreateWeek(event.block.timestamp); const month = getOrCreateMonth(event.block.timestamp); - const total = getTotal(); - - day.realizedGain = day.realizedGain.plus(amount); - week.realizedGain = week.realizedGain.plus(amount); - month.realizedGain = month.realizedGain.plus(amount); - total.realizedGain = total.realizedGain.plus(amount); - account.realizedGain = account.realizedGain.plus(amount); - - day.save(); - week.save(); - month.save(); - total.save(); - account.save(); + + const dailyEarnFlow = getOrCreateDailyEarnFlow(day, token, protocol); + const weeklyEarnFlow = getOrCreateWeeklyEarnFlow(week, token, protocol); + const monthlyEarnFlow = getOrCreateMonthlyEarnFlow(month, token, protocol); + const earnPosition = getOrCreateEarnPosition( + account, + pool, + token, + protocol, + ); + + dailyEarnFlow.claimedGain = dailyEarnFlow.claimedGain.plus(amount); + weeklyEarnFlow.claimedGain = weeklyEarnFlow.claimedGain.plus(amount); + monthlyEarnFlow.claimedGain = monthlyEarnFlow.claimedGain.plus(amount); + earnPosition.normalGain = earnPosition.normalGain.plus(amount); } From 183aa3d29ccc46f2ba42f1fa6d7c1be61f9bee51 Mon Sep 17 00:00:00 2001 From: tahos81 Date: Tue, 18 Jun 2024 17:30:45 +0300 Subject: [PATCH 022/103] Update subgraph.yaml --- subgraph/subgraph.yaml | 44 +++++++++++++++++++----------------------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/subgraph/subgraph.yaml b/subgraph/subgraph.yaml index 02fc42f..57d50b6 100644 --- a/subgraph/subgraph.yaml +++ b/subgraph/subgraph.yaml @@ -10,17 +10,16 @@ dataSources: source: address: '0x9d2811b85c1d736427722817b69e4d1e98016bb0' abi: KoiPair - startBlock: 24799912 + startBlock: 30099184 mapping: kind: ethereum/events apiVersion: 0.0.7 language: wasm/assemblyscript entities: - - Total - - Day - - Week - - Month - - ClaveAccount + - DailyEarnFlow + - WeeklyEarnFlow + - MonthlyEarnFlow + - EarnPosition abis: - name: KoiPair file: ./abis/KoiPair.json @@ -260,17 +259,16 @@ dataSources: source: address: '0x12e7A9423d9128287E63017eE6d1f20e1C237f15' abi: SyncStablePool - startBlock: 24799912 + startBlock: 30099184 mapping: kind: ethereum/events apiVersion: 0.0.7 language: wasm/assemblyscript entities: - - Total - - Day - - Week - - Month - - ClaveAccount + - DailyEarnFlow + - WeeklyEarnFlow + - MonthlyEarnFlow + - EarnPosition abis: - name: SyncStablePool file: ./abis/SyncStablePool.json @@ -286,17 +284,16 @@ dataSources: source: address: '0x2B9a7d5cD64E5c1446b32e034e75A5C93B0C8bB5' abi: SyncStaking - startBlock: 24799912 + startBlock: 30099184 mapping: kind: ethereum/events apiVersion: 0.0.7 language: wasm/assemblyscript entities: - - Total - - Day - - Week - - Month - - ClaveAccount + - DailyEarnFlow + - WeeklyEarnFlow + - MonthlyEarnFlow + - EarnPosition abis: - name: SyncStaking file: ./abis/SyncStaking.json @@ -310,17 +307,16 @@ dataSources: source: address: '0x4d9429246EA989C9CeE203B43F6d1C7D83e3B8F8' abi: zeroUsdtPool - startBlock: 24799912 + startBlock: 30099184 mapping: kind: ethereum/events apiVersion: 0.0.7 language: wasm/assemblyscript entities: - - Total - - Day - - Week - - Month - - ClaveAccount + - DailyEarnFlow + - WeeklyEarnFlow + - MonthlyEarnFlow + - EarnPosition abis: - name: zeroUsdtPool file: ./abis/zeroUsdtPool.json From 8be1884776f4d05a46f0b2ea8a3ef884530b7237 Mon Sep 17 00:00:00 2001 From: tahos81 Date: Tue, 18 Jun 2024 23:30:47 +0300 Subject: [PATCH 023/103] feat: ABI for clave zk stake --- subgraph/abis/ClaveZKStake.json | 444 ++++++++++++++++++++++++++++++++ 1 file changed, 444 insertions(+) create mode 100644 subgraph/abis/ClaveZKStake.json diff --git a/subgraph/abis/ClaveZKStake.json b/subgraph/abis/ClaveZKStake.json new file mode 100644 index 0000000..0929591 --- /dev/null +++ b/subgraph/abis/ClaveZKStake.json @@ -0,0 +1,444 @@ +[ + { + "inputs": [ + { + "internalType": "uint256", + "name": "_limitPerUser", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_totalLimit", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reward", + "type": "uint256" + } + ], + "name": "RewardPaid", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Staked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Withdrawn", + "type": "event" + }, + { + "inputs": [], + "name": "ZK", + "outputs": [ + { + "internalType": "contract IERC20", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "duration", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_account", + "type": "address" + } + ], + "name": "earned", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "finishAt", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getApy", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getReward", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "lastTimeRewardApplicable", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "limitPerUser", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "notifyRewardAmount", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "registry1", + "outputs": [ + { + "internalType": "contract IClaveRegistry", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "registry2", + "outputs": [ + { + "internalType": "contract IClaveRegistry", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "rewardPerToken", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "rewardPerTokenStored", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "rewardRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "rewards", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_limitPerUser", + "type": "uint256" + } + ], + "name": "setLimitPerUser", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_duration", + "type": "uint256" + } + ], + "name": "setRewardsDuration", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_totalLimit", + "type": "uint256" + } + ], + "name": "setTotalLimit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "stake", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "totalLimit", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "updatedAt", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "userRewardPerTokenPaid", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "withdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "withdrawReward", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] From be90d776306529b848059674bc919ff7510c03d9 Mon Sep 17 00:00:00 2001 From: tahos81 Date: Tue, 18 Jun 2024 23:31:02 +0300 Subject: [PATCH 024/103] feat: index clave zk stake --- subgraph/src/clave-zk-stake.ts | 126 +++++++++++++++++++++++++++++++++ subgraph/subgraph.yaml | 27 +++++++ 2 files changed, 153 insertions(+) create mode 100644 subgraph/src/clave-zk-stake.ts diff --git a/subgraph/src/clave-zk-stake.ts b/subgraph/src/clave-zk-stake.ts new file mode 100644 index 0000000..aeceb89 --- /dev/null +++ b/subgraph/src/clave-zk-stake.ts @@ -0,0 +1,126 @@ +/** + * Copyright Clave - All Rights Reserved + * Unauthorized copying of this file, via any medium is strictly prohibited + * Proprietary and confidential + */ + +/* eslint-disable @typescript-eslint/consistent-type-imports */ +import { Address } from '@graphprotocol/graph-ts'; + +import { + RewardPaid as RewardPaidEvent, + Staked as StakedEvent, + Withdrawn as WithdrawnEvent, +} from '../generated/ClaveZKStake/ClaveZKStake'; +import { ClaveAccount } from '../generated/schema'; +import { + getOrCreateDailyEarnFlow, + getOrCreateDay, + getOrCreateEarnPosition, + getOrCreateMonth, + getOrCreateMonthlyEarnFlow, + getOrCreateWeek, + getOrCreateWeeklyEarnFlow, +} from './helpers'; + +const protocol = 'Clave'; +const token = Address.fromHexString( + '0x5A7d6b2F92C77FAD6CCaBd7EE0624E64907Eaf3E', +); + +export function handleStaked(event: StakedEvent): void { + const account = ClaveAccount.load(event.params.user); + if (!account) { + return; + } + + const pool = event.address; + const amount = event.params.amount; + + const day = getOrCreateDay(event.block.timestamp); + const week = getOrCreateWeek(event.block.timestamp); + const month = getOrCreateMonth(event.block.timestamp); + + const dailyEarnFlow = getOrCreateDailyEarnFlow(day, token, protocol); + const weeklyEarnFlow = getOrCreateWeeklyEarnFlow(week, token, protocol); + const monthlyEarnFlow = getOrCreateMonthlyEarnFlow(month, token, protocol); + const earnPosition = getOrCreateEarnPosition( + account, + pool, + token, + protocol, + ); + + dailyEarnFlow.amountIn = dailyEarnFlow.amountIn.plus(amount); + weeklyEarnFlow.amountIn = weeklyEarnFlow.amountIn.plus(amount); + monthlyEarnFlow.amountIn = monthlyEarnFlow.amountIn.plus(amount); + earnPosition.invested = earnPosition.invested.plus(amount); + + dailyEarnFlow.save(); + weeklyEarnFlow.save(); + monthlyEarnFlow.save(); + earnPosition.save(); +} + +export function handleWithdrawn(event: WithdrawnEvent): void { + const account = ClaveAccount.load(event.transaction.from); + if (!account) { + return; + } + + const pool = event.address; + const amount = event.params.amount; + + const day = getOrCreateDay(event.block.timestamp); + const week = getOrCreateWeek(event.block.timestamp); + const month = getOrCreateMonth(event.block.timestamp); + + const dailyEarnFlow = getOrCreateDailyEarnFlow(day, token, protocol); + const weeklyEarnFlow = getOrCreateWeeklyEarnFlow(week, token, protocol); + const monthlyEarnFlow = getOrCreateMonthlyEarnFlow(month, token, protocol); + const earnPosition = getOrCreateEarnPosition( + account, + pool, + token, + protocol, + ); + + dailyEarnFlow.amountOut = dailyEarnFlow.amountOut.plus(amount); + weeklyEarnFlow.amountOut = weeklyEarnFlow.amountOut.plus(amount); + monthlyEarnFlow.amountOut = monthlyEarnFlow.amountOut.plus(amount); + earnPosition.invested = earnPosition.invested.minus(amount); + + dailyEarnFlow.save(); + weeklyEarnFlow.save(); + monthlyEarnFlow.save(); + earnPosition.save(); +} + +export function handleRewardPaid(event: RewardPaidEvent): void { + const account = ClaveAccount.load(event.params.user); + if (!account) { + return; + } + + const pool = event.address; + const amount = event.params.reward; + + const day = getOrCreateDay(event.block.timestamp); + const week = getOrCreateWeek(event.block.timestamp); + const month = getOrCreateMonth(event.block.timestamp); + + const dailyEarnFlow = getOrCreateDailyEarnFlow(day, token, protocol); + const weeklyEarnFlow = getOrCreateWeeklyEarnFlow(week, token, protocol); + const monthlyEarnFlow = getOrCreateMonthlyEarnFlow(month, token, protocol); + const earnPosition = getOrCreateEarnPosition( + account, + pool, + token, + protocol, + ); + + dailyEarnFlow.claimedGain = dailyEarnFlow.claimedGain.plus(amount); + weeklyEarnFlow.claimedGain = weeklyEarnFlow.claimedGain.plus(amount); + monthlyEarnFlow.claimedGain = monthlyEarnFlow.claimedGain.plus(amount); + earnPosition.normalGain = earnPosition.normalGain.plus(amount); +} diff --git a/subgraph/subgraph.yaml b/subgraph/subgraph.yaml index 57d50b6..6a9b378 100644 --- a/subgraph/subgraph.yaml +++ b/subgraph/subgraph.yaml @@ -326,6 +326,33 @@ dataSources: - event: Withdraw(indexed address,indexed address,indexed address,uint256) handler: handleWithdraw file: ./src/zero-usdt-pool.ts + - kind: ethereum + name: ClaveZKStake + network: zksync-era + source: + address: '0xd7c75bDe1F5Dfa164943cb9b6F1017b33E75D7a2' + abi: ClaveZKStake + startBlock: 36908411 + mapping: + kind: ethereum/events + apiVersion: 0.0.7 + language: wasm/assemblyscript + entities: + - DailyEarnFlow + - WeeklyEarnFlow + - MonthlyEarnFlow + - EarnPosition + abis: + - name: ClaveZKStake + file: ./abis/ClaveZKStake.json + eventHandlers: + - event: Staked(indexed address,uint256) + handler: handleStaked + - event: Withdrawn(indexed address,uint256) + handler: handleWithdrawn + - event: RewardPaid(indexed address,uint256) + handler: handleRewardPaid + file: ./src/clave-zk-stake.ts templates: - kind: ethereum name: Account From 4490f91cbd430cfe6d160aab6fa0d714f5950862 Mon Sep 17 00:00:00 2001 From: tahos81 Date: Tue, 18 Jun 2024 23:31:10 +0300 Subject: [PATCH 025/103] fix small bugs --- subgraph/src/account-factory-v2.ts | 8 -------- subgraph/src/account-factory.ts | 8 -------- subgraph/src/helpers.ts | 12 ++++++------ 3 files changed, 6 insertions(+), 22 deletions(-) diff --git a/subgraph/src/account-factory-v2.ts b/subgraph/src/account-factory-v2.ts index b455424..cd795e5 100644 --- a/subgraph/src/account-factory-v2.ts +++ b/subgraph/src/account-factory-v2.ts @@ -43,10 +43,6 @@ export function handleClaveAccountCreated( account.isRecovering = false; account.recoveryCount = 0; account.txCount = 0; - account.invested = ZERO; - account.realizedGain = ZERO; - account.investedEth = ZERO; - account.realizedGainEth = ZERO; day.save(); week.save(); @@ -76,10 +72,6 @@ export function handleClaveAccountDeployed( account.recoveryCount = 0; account.txCount = 0; account.creationDate = ZERO; - account.invested = ZERO; - account.realizedGain = ZERO; - account.investedEth = ZERO; - account.realizedGainEth = ZERO; } account.implementation = Bytes.fromHexString( diff --git a/subgraph/src/account-factory.ts b/subgraph/src/account-factory.ts index 7b4f563..1d94c0f 100644 --- a/subgraph/src/account-factory.ts +++ b/subgraph/src/account-factory.ts @@ -50,10 +50,6 @@ export function handleOnce(_block: ethereum.Block): void { account.isRecovering = false; account.recoveryCount = 0; account.txCount = 0; - account.invested = ZERO; - account.realizedGain = ZERO; - account.investedEth = ZERO; - account.realizedGainEth = ZERO; day.save(); week.save(); @@ -82,10 +78,6 @@ export function handleNewClaveAccount(event: NewClaveAccountEvent): void { account.recoveryCount = 0; account.txCount = 0; account.creationDate = ZERO; - account.invested = ZERO; - account.realizedGain = ZERO; - account.investedEth = ZERO; - account.realizedGainEth = ZERO; } account.implementation = Bytes.fromHexString( '0xdd4dD37B22Fc16DBFF3daB6Ecd681798c459f275', diff --git a/subgraph/src/helpers.ts b/subgraph/src/helpers.ts index cf76b9d..c87a58c 100644 --- a/subgraph/src/helpers.ts +++ b/subgraph/src/helpers.ts @@ -9,7 +9,7 @@ /* eslint-disable @typescript-eslint/consistent-type-imports */ /* eslint-disable prefer-const */ -import { ByteArray, Bytes } from '@graphprotocol/graph-ts'; +import { Bytes } from '@graphprotocol/graph-ts'; import { BigInt } from '@graphprotocol/graph-ts'; import { @@ -178,8 +178,8 @@ export function getTotal(): Total { export function getOrCreateEarnPosition( account: ClaveAccount, - pool: ByteArray, - token: ByteArray, + pool: Bytes, + token: Bytes, protocol: string, ): EarnPosition { let earnPositionId = account.id.concat(pool).concat(token); @@ -203,7 +203,7 @@ export function getOrCreateEarnPosition( export function getOrCreateDailyEarnFlow( day: Day, - token: ByteArray, + token: Bytes, protocol: string, ): DailyEarnFlow { let dailyEarnFlowId = day.id.concat(token).concat(Bytes.fromUTF8(protocol)); @@ -226,7 +226,7 @@ export function getOrCreateDailyEarnFlow( export function getOrCreateWeeklyEarnFlow( week: Week, - token: ByteArray, + token: Bytes, protocol: string, ): WeeklyEarnFlow { let weeklyEarnFlowId = week.id @@ -251,7 +251,7 @@ export function getOrCreateWeeklyEarnFlow( export function getOrCreateMonthlyEarnFlow( month: Month, - token: ByteArray, + token: Bytes, protocol: string, ): MonthlyEarnFlow { let monthlyEarnFlowId = month.id From 36263c92e5c0492e3625c79215be5453048ac6b5 Mon Sep 17 00:00:00 2001 From: tahos81 Date: Thu, 20 Jun 2024 12:27:01 +0300 Subject: [PATCH 026/103] fix zk stake address --- subgraph/subgraph.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subgraph/subgraph.yaml b/subgraph/subgraph.yaml index 6a9b378..d70c010 100644 --- a/subgraph/subgraph.yaml +++ b/subgraph/subgraph.yaml @@ -330,7 +330,7 @@ dataSources: name: ClaveZKStake network: zksync-era source: - address: '0xd7c75bDe1F5Dfa164943cb9b6F1017b33E75D7a2' + address: '0x9248F1Ee8cBD029F3D22A92EB270333a39846fB2' abi: ClaveZKStake startBlock: 36908411 mapping: From 2ad16a8e6a647fa59e9fffac97a505f25aca3670 Mon Sep 17 00:00:00 2001 From: tahos81 Date: Mon, 22 Jul 2024 08:11:02 +0300 Subject: [PATCH 027/103] feat: add new syncswap clave pool --- subgraph/abis/SyncClaveStaking.json | 725 ++++++++++++++++++++++++++++ subgraph/src/sync-clave-staking.ts | 54 +++ subgraph/subgraph.yaml | 23 + 3 files changed, 802 insertions(+) create mode 100644 subgraph/abis/SyncClaveStaking.json create mode 100644 subgraph/src/sync-clave-staking.ts diff --git a/subgraph/abis/SyncClaveStaking.json b/subgraph/abis/SyncClaveStaking.json new file mode 100644 index 0000000..1b876da --- /dev/null +++ b/subgraph/abis/SyncClaveStaking.json @@ -0,0 +1,725 @@ +[ + { + "inputs": [ + { "internalType": "address", "name": "_shareToken", "type": "address" }, + { + "internalType": "address", + "name": "_rewardBatchClaimer", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { "inputs": [], "name": "SafeTransferFailed", "type": "error" }, + { "inputs": [], "name": "SafeTransferFromFailed", "type": "error" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "AddRewardToken", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "ClaimRewards", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "RemoveRewardToken", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "emergency", + "type": "bool" + } + ], + "name": "SetEmergency", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "rewardRate", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "rewardAmount", + "type": "uint256" + } + ], + "name": "SetRewardRate", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "rewarder", + "type": "address" + } + ], + "name": "SetRewarder", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint24", + "name": "stakingFee", + "type": "uint24" + } + ], + "name": "SetStakingFee", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "stakingFeeRecipient", + "type": "address" + } + ], + "name": "SetStakingFeeRecipient", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "Stake", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "rewardToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "leftover", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newRewardRate", + "type": "uint256" + } + ], + "name": "SupplyRewards", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "feeAmount", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "Withdraw", + "type": "event" + }, + { + "inputs": [], + "name": "_status", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "token", "type": "address" } + ], + "name": "addRewardToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "amount", "type": "uint256" }, + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "uint8", "name": "mode", "type": "uint8" }, + { "internalType": "bytes", "name": "claimData", "type": "bytes" } + ], + "name": "claimAndWithdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "account", "type": "address" }, + { "internalType": "uint8", "name": "mode", "type": "uint8" }, + { "internalType": "bytes", "name": "claimData", "type": "bytes" } + ], + "name": "claimRewards", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "claveRegistry", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "emergency", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "to", "type": "address" }], + "name": "emergencyWithdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "rewardToken", "type": "address" } + ], + "name": "getRewardSnapshots", + "outputs": [ + { + "components": [ + { "internalType": "uint256", "name": "timestamp", "type": "uint256" }, + { "internalType": "uint256", "name": "leftover", "type": "uint256" }, + { "internalType": "uint256", "name": "supplied", "type": "uint256" }, + { + "internalType": "uint256", + "name": "newRewardRate", + "type": "uint256" + } + ], + "internalType": "struct SyncSwapStakingClave.RewardSnapshot[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "isRewardToken", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "token", "type": "address" }, + { "internalType": "bool", "name": "updateArray", "type": "bool" } + ], + "name": "removeRewardToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_rewardToken", "type": "address" }, + { "internalType": "uint256", "name": "index", "type": "uint256" } + ], + "name": "removeRewardTokenFromArray", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "token", "type": "address" }, + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "rescueERC20", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address payable", "name": "to", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "rescueETH", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "rewardBatchClaimer", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "rewardClaimers", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "rewardToken", "type": "address" } + ], + "name": "rewardData", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "rewardRate", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "rewardAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lastUpdate", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "rewardPerShare", + "type": "uint256" + } + ], + "internalType": "struct IStaking.RewardData", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "rewardDuration", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "", "type": "address" }, + { "internalType": "uint256", "name": "", "type": "uint256" } + ], + "name": "rewardSnapshots", + "outputs": [ + { "internalType": "uint256", "name": "timestamp", "type": "uint256" }, + { "internalType": "uint256", "name": "leftover", "type": "uint256" }, + { "internalType": "uint256", "name": "supplied", "type": "uint256" }, + { "internalType": "uint256", "name": "newRewardRate", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "rewardToken", "type": "address" } + ], + "name": "rewardSnapshotsLength", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "name": "rewardTokens", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "rewardTokensLength", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bool", "name": "_emergency", "type": "bool" } + ], + "name": "setEmergency", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_rewardBatchClaimer", + "type": "address" + } + ], + "name": "setRewardBatchClaimer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" }, + { "internalType": "address", "name": "_rewardClaimer", "type": "address" } + ], + "name": "setRewardClaimer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_rewardDuration", + "type": "uint256" + } + ], + "name": "setRewardDuration", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" }, + { + "internalType": "uint256", + "name": "_newRewardRate", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_newRewardAmount", + "type": "uint256" + }, + { "internalType": "bool", "name": "shouldUpdate", "type": "bool" } + ], + "name": "setRewardRate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "index", "type": "uint256" }, + { "internalType": "address", "name": "oldToken", "type": "address" }, + { "internalType": "address", "name": "newToken", "type": "address" } + ], + "name": "setRewardToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint24", "name": "_stakingFee", "type": "uint24" } + ], + "name": "setStakingFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_stakingFeeRecipient", + "type": "address" + } + ], + "name": "setStakingFeeRecipient", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "shareToken", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "amount", "type": "uint256" }, + { "internalType": "address", "name": "to", "type": "address" } + ], + "name": "stake", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "stakingFee", + "outputs": [{ "internalType": "uint24", "name": "", "type": "uint24" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "stakingFeeRecipient", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "token", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "supplyRewards", + "outputs": [ + { "internalType": "bool", "name": "", "type": "bool" }, + { "internalType": "uint256", "name": "", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "totalRewardAmount", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalStaked", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "newOwner", "type": "address" } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "account", "type": "address" }, + { "internalType": "uint8", "name": "mode", "type": "uint8" }, + { "internalType": "bytes", "name": "claimData", "type": "bytes" } + ], + "name": "update", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_claveRegistry", "type": "address" } + ], + "name": "updateClaveRegistry", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "rewardToken", "type": "address" }, + { "internalType": "address", "name": "account", "type": "address" } + ], + "name": "userRewardData", + "outputs": [ + { + "components": [ + { "internalType": "uint256", "name": "claimable", "type": "uint256" }, + { + "internalType": "uint256", + "name": "debtRewardPerShare", + "type": "uint256" + } + ], + "internalType": "struct IStaking.UserRewardData", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "userStaked", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "amount", "type": "uint256" }, + { "internalType": "address", "name": "to", "type": "address" } + ], + "name": "withdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/subgraph/src/sync-clave-staking.ts b/subgraph/src/sync-clave-staking.ts new file mode 100644 index 0000000..dd98b15 --- /dev/null +++ b/subgraph/src/sync-clave-staking.ts @@ -0,0 +1,54 @@ +/** + * Copyright Clave - All Rights Reserved + * Unauthorized copying of this file, via any medium is strictly prohibited + * Proprietary and confidential + */ + +/* eslint-disable @typescript-eslint/consistent-type-imports */ +import { ClaimRewards as ClaimRewardsEvent } from '../generated/SyncClaveStaking/SyncClaveStaking'; +import { ClaveAccount } from '../generated/schema'; +import { + getOrCreateDailyEarnFlow, + getOrCreateDay, + getOrCreateEarnPosition, + getOrCreateMonth, + getOrCreateMonthlyEarnFlow, + getOrCreateWeek, + getOrCreateWeeklyEarnFlow, +} from './helpers'; + +const protocol = 'SyncSwap'; + +export function handleClaimRewards(event: ClaimRewardsEvent): void { + const account = ClaveAccount.load(event.params.account); + if (!account) { + return; + } + + const pool = event.address; + const token = event.params.token; + const amount = event.params.amount; + + const day = getOrCreateDay(event.block.timestamp); + const week = getOrCreateWeek(event.block.timestamp); + const month = getOrCreateMonth(event.block.timestamp); + const dailyEarnFlow = getOrCreateDailyEarnFlow(day, token, protocol); + const weeklyEarnFlow = getOrCreateWeeklyEarnFlow(week, token, protocol); + const monthlyEarnFlow = getOrCreateMonthlyEarnFlow(month, token, protocol); + const earnPosition = getOrCreateEarnPosition( + account, + pool, + token, + protocol, + ); + + dailyEarnFlow.claimedGain = dailyEarnFlow.claimedGain.plus(amount); + weeklyEarnFlow.claimedGain = weeklyEarnFlow.claimedGain.plus(amount); + monthlyEarnFlow.claimedGain = monthlyEarnFlow.claimedGain.plus(amount); + earnPosition.normalGain = earnPosition.normalGain.plus(amount); + + dailyEarnFlow.save(); + weeklyEarnFlow.save(); + monthlyEarnFlow.save(); + earnPosition.save(); +} diff --git a/subgraph/subgraph.yaml b/subgraph/subgraph.yaml index d70c010..691a565 100644 --- a/subgraph/subgraph.yaml +++ b/subgraph/subgraph.yaml @@ -301,6 +301,29 @@ dataSources: - event: ClaimRewards(indexed address,indexed address,uint256) handler: handleClaimRewards file: ./src/sync-staking.ts + - kind: ethereum + name: SyncClaveStaking + network: zksync-era + source: + address: '0x6fEbba4a360F43B71560519bCD90B3F45c8F441E' + abi: SyncClaveStaking + startBlock: 39511808 + mapping: + kind: ethereum/events + apiVersion: 0.0.7 + language: wasm/assemblyscript + entities: + - DailyEarnFlow + - WeeklyEarnFlow + - MonthlyEarnFlow + - EarnPosition + abis: + - name: SyncClaveStaking + file: ./abis/SyncClaveStaking.json + eventHandlers: + - event: ClaimRewards(indexed address,indexed address,uint256) + handler: handleClaimRewards + file: ./src/sync-staking.ts - kind: ethereum name: zeroUsdtPool network: zksync-era From b958606773244e3a99073c8686fa8955b735a3f3 Mon Sep 17 00:00:00 2001 From: tahos81 Date: Mon, 22 Jul 2024 08:11:18 +0300 Subject: [PATCH 028/103] fix sync pool mapping file --- subgraph/src/sync-stable-pair.ts | 77 ++++++++++++++++++++++++++++---- 1 file changed, 69 insertions(+), 8 deletions(-) diff --git a/subgraph/src/sync-stable-pair.ts b/subgraph/src/sync-stable-pair.ts index dda5369..192b6a0 100644 --- a/subgraph/src/sync-stable-pair.ts +++ b/subgraph/src/sync-stable-pair.ts @@ -27,6 +27,9 @@ const protocol = 'SyncSwap'; const token = Address.fromHexString( '0x000000000000000000000000000000000000800A', ); +const tokenb = Address.fromHexString( + '0x703b52F2b28fEbcB60E1372858AF5b18849FE867', +); export function handleMint(event: MintEvent): void { const account = ClaveAccount.load(event.transaction.from); @@ -38,10 +41,6 @@ export function handleMint(event: MintEvent): void { const amount0 = event.params.amount0; const amount1 = event.params.amount1; - if (amount1.gt(ZERO)) { - return; - } - const day = getOrCreateDay(event.block.timestamp); const week = getOrCreateWeek(event.block.timestamp); const month = getOrCreateMonth(event.block.timestamp); @@ -61,10 +60,34 @@ export function handleMint(event: MintEvent): void { monthlyEarnFlow.amountIn = monthlyEarnFlow.amountIn.plus(amount0); earnPosition.invested = earnPosition.invested.plus(amount0); + const dailyEarnFlow2 = getOrCreateDailyEarnFlow(day, tokenb, protocol); + const weeklyEarnFlow2 = getOrCreateWeeklyEarnFlow(week, tokenb, protocol); + const monthlyEarnFlow2 = getOrCreateMonthlyEarnFlow( + month, + tokenb, + protocol, + ); + const earnPosition2 = getOrCreateEarnPosition( + account, + pool, + tokenb, + protocol, + ); + + dailyEarnFlow2.amountIn = dailyEarnFlow2.amountIn.plus(amount1); + weeklyEarnFlow2.amountIn = weeklyEarnFlow2.amountIn.plus(amount1); + monthlyEarnFlow2.amountIn = monthlyEarnFlow2.amountIn.plus(amount1); + earnPosition2.invested = earnPosition2.invested.plus(amount1); + dailyEarnFlow.save(); weeklyEarnFlow.save(); monthlyEarnFlow.save(); earnPosition.save(); + + dailyEarnFlow2.save(); + weeklyEarnFlow2.save(); + monthlyEarnFlow2.save(); + earnPosition2.save(); } export function handleBurn(event: BurnEvent): void { @@ -77,10 +100,6 @@ export function handleBurn(event: BurnEvent): void { const amount0 = event.params.amount0; const amount1 = event.params.amount1; - if (amount1.gt(ZERO)) { - return; - } - const day = getOrCreateDay(event.block.timestamp); const week = getOrCreateWeek(event.block.timestamp); const month = getOrCreateMonth(event.block.timestamp); @@ -118,8 +137,50 @@ export function handleBurn(event: BurnEvent): void { earnPosition.invested = earnPosition.invested.minus(amount0); } + const dailyEarnFlow2 = getOrCreateDailyEarnFlow(day, tokenb, protocol); + const weeklyEarnFlow2 = getOrCreateWeeklyEarnFlow(week, tokenb, protocol); + const monthlyEarnFlow2 = getOrCreateMonthlyEarnFlow( + month, + tokenb, + protocol, + ); + const earnPosition2 = getOrCreateEarnPosition( + account, + pool, + tokenb, + protocol, + ); + + const invested2 = earnPosition2.invested; + + if (amount1.gt(invested2)) { + const compoundGain2 = amount1.minus(invested2); + dailyEarnFlow2.amountOut = dailyEarnFlow2.amountOut.plus(invested2); + dailyEarnFlow2.claimedGain = + dailyEarnFlow2.claimedGain.plus(compoundGain2); + weeklyEarnFlow2.amountOut = weeklyEarnFlow2.amountOut.plus(invested2); + weeklyEarnFlow2.claimedGain = + weeklyEarnFlow2.claimedGain.plus(compoundGain2); + monthlyEarnFlow2.amountOut = monthlyEarnFlow2.amountOut.plus(invested2); + monthlyEarnFlow2.claimedGain = + monthlyEarnFlow2.claimedGain.plus(compoundGain2); + earnPosition2.invested = ZERO; + earnPosition2.compoundGain = + earnPosition2.compoundGain.plus(compoundGain2); + } else { + dailyEarnFlow2.amountOut = dailyEarnFlow2.amountOut.plus(amount1); + weeklyEarnFlow2.amountOut = weeklyEarnFlow2.amountOut.plus(amount1); + monthlyEarnFlow2.amountOut = monthlyEarnFlow2.amountOut.plus(amount1); + earnPosition2.invested = earnPosition2.invested.minus(amount1); + } + dailyEarnFlow.save(); weeklyEarnFlow.save(); monthlyEarnFlow.save(); earnPosition.save(); + + dailyEarnFlow2.save(); + weeklyEarnFlow2.save(); + monthlyEarnFlow2.save(); + earnPosition2.save(); } From f9ced1f04511732b92ac3b4e4a4a05ed57f28505 Mon Sep 17 00:00:00 2001 From: tahos81 Date: Tue, 13 Aug 2024 21:23:36 +0300 Subject: [PATCH 029/103] meow staking --- subgraph/schema.graphql | 1 + subgraph/src/account-factory.ts | 2 +- subgraph/src/meow-staking.ts | 129 ++++++++++++++++++++++++++++++++ subgraph/subgraph.yaml | 27 +++++++ 4 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 subgraph/src/meow-staking.ts diff --git a/subgraph/schema.graphql b/subgraph/schema.graphql index 68cf6b3..f4ee7d8 100644 --- a/subgraph/schema.graphql +++ b/subgraph/schema.graphql @@ -49,6 +49,7 @@ enum EarnProtocol { SyncSwap ZeroLend Clave + Meow } type EarnPosition @entity { diff --git a/subgraph/src/account-factory.ts b/subgraph/src/account-factory.ts index 1d94c0f..5ef731f 100644 --- a/subgraph/src/account-factory.ts +++ b/subgraph/src/account-factory.ts @@ -66,7 +66,7 @@ export function handleNewClaveAccount(event: NewClaveAccountEvent): void { const week = getOrCreateWeek(event.block.timestamp); const month = getOrCreateMonth(event.block.timestamp); const total = getTotal(); - day.createdAccounts = day.createdAccounts + 1; + day.deployedAccounts = day.deployedAccounts + 1; week.deployedAccounts = week.deployedAccounts + 1; month.deployedAccounts = month.deployedAccounts + 1; total.deployedAccounts = total.deployedAccounts + 1; diff --git a/subgraph/src/meow-staking.ts b/subgraph/src/meow-staking.ts new file mode 100644 index 0000000..76ae80a --- /dev/null +++ b/subgraph/src/meow-staking.ts @@ -0,0 +1,129 @@ +/** + * Copyright Clave - All Rights Reserved + * Unauthorized copying of this file, via any medium is strictly prohibited + * Proprietary and confidential + */ + +/* eslint-disable @typescript-eslint/consistent-type-imports */ +import { Address } from '@graphprotocol/graph-ts'; + +import { + RewardPaid as RewardPaidEvent, + Staked as StakedEvent, + Withdrawn as WithdrawnEvent, +} from '../generated/ClaveZKStake/ClaveZKStake'; +import { ClaveAccount } from '../generated/schema'; +import { + getOrCreateDailyEarnFlow, + getOrCreateDay, + getOrCreateEarnPosition, + getOrCreateMonth, + getOrCreateMonthlyEarnFlow, + getOrCreateWeek, + getOrCreateWeeklyEarnFlow, +} from './helpers'; + +const protocol = 'Meow'; +const token = Address.fromHexString( + '0x79db8c67d0c33203da4Efb58F7D325E1e0d4d692', +); +const reward = Address.fromHexString( + '0x5A7d6b2F92C77FAD6CCaBd7EE0624E64907Eaf3E', +); + +export function handleStaked(event: StakedEvent): void { + const account = ClaveAccount.load(event.params.user); + if (!account) { + return; + } + + const pool = event.address; + const amount = event.params.amount; + + const day = getOrCreateDay(event.block.timestamp); + const week = getOrCreateWeek(event.block.timestamp); + const month = getOrCreateMonth(event.block.timestamp); + + const dailyEarnFlow = getOrCreateDailyEarnFlow(day, token, protocol); + const weeklyEarnFlow = getOrCreateWeeklyEarnFlow(week, token, protocol); + const monthlyEarnFlow = getOrCreateMonthlyEarnFlow(month, token, protocol); + const earnPosition = getOrCreateEarnPosition( + account, + pool, + token, + protocol, + ); + + dailyEarnFlow.amountIn = dailyEarnFlow.amountIn.plus(amount); + weeklyEarnFlow.amountIn = weeklyEarnFlow.amountIn.plus(amount); + monthlyEarnFlow.amountIn = monthlyEarnFlow.amountIn.plus(amount); + earnPosition.invested = earnPosition.invested.plus(amount); + + dailyEarnFlow.save(); + weeklyEarnFlow.save(); + monthlyEarnFlow.save(); + earnPosition.save(); +} + +export function handleWithdrawn(event: WithdrawnEvent): void { + const account = ClaveAccount.load(event.transaction.from); + if (!account) { + return; + } + + const pool = event.address; + const amount = event.params.amount; + + const day = getOrCreateDay(event.block.timestamp); + const week = getOrCreateWeek(event.block.timestamp); + const month = getOrCreateMonth(event.block.timestamp); + + const dailyEarnFlow = getOrCreateDailyEarnFlow(day, token, protocol); + const weeklyEarnFlow = getOrCreateWeeklyEarnFlow(week, token, protocol); + const monthlyEarnFlow = getOrCreateMonthlyEarnFlow(month, token, protocol); + const earnPosition = getOrCreateEarnPosition( + account, + pool, + token, + protocol, + ); + + dailyEarnFlow.amountOut = dailyEarnFlow.amountOut.plus(amount); + weeklyEarnFlow.amountOut = weeklyEarnFlow.amountOut.plus(amount); + monthlyEarnFlow.amountOut = monthlyEarnFlow.amountOut.plus(amount); + earnPosition.invested = earnPosition.invested.minus(amount); + + dailyEarnFlow.save(); + weeklyEarnFlow.save(); + monthlyEarnFlow.save(); + earnPosition.save(); +} + +export function handleRewardPaid(event: RewardPaidEvent): void { + const account = ClaveAccount.load(event.params.user); + if (!account) { + return; + } + + const pool = event.address; + const amount = event.params.reward; + + const day = getOrCreateDay(event.block.timestamp); + const week = getOrCreateWeek(event.block.timestamp); + const month = getOrCreateMonth(event.block.timestamp); + + const dailyEarnFlow = getOrCreateDailyEarnFlow(day, reward, protocol); + const weeklyEarnFlow = getOrCreateWeeklyEarnFlow(week, reward, protocol); + const monthlyEarnFlow = getOrCreateMonthlyEarnFlow(month, reward, protocol); + const earnPosition = getOrCreateEarnPosition( + account, + pool, + reward, + protocol, + ); + + dailyEarnFlow.claimedGain = dailyEarnFlow.claimedGain.plus(amount); + weeklyEarnFlow.claimedGain = weeklyEarnFlow.claimedGain.plus(amount); + monthlyEarnFlow.claimedGain = monthlyEarnFlow.claimedGain.plus(amount); + earnPosition.normalGain = earnPosition.normalGain.plus(amount); +} diff --git a/subgraph/subgraph.yaml b/subgraph/subgraph.yaml index 691a565..25dc380 100644 --- a/subgraph/subgraph.yaml +++ b/subgraph/subgraph.yaml @@ -376,6 +376,33 @@ dataSources: - event: RewardPaid(indexed address,uint256) handler: handleRewardPaid file: ./src/clave-zk-stake.ts + - kind: ethereum + name: MeowStake + network: zksync-era + source: + address: '0x0C71c7B6FD654EE0D3137a2Eb790CAd8Ba702540' + abi: ClaveZKStake + startBlock: 36908411 + mapping: + kind: ethereum/events + apiVersion: 0.0.7 + language: wasm/assemblyscript + entities: + - DailyEarnFlow + - WeeklyEarnFlow + - MonthlyEarnFlow + - EarnPosition + abis: + - name: ClaveZKStake + file: ./abis/ClaveZKStake.json + eventHandlers: + - event: Staked(indexed address,uint256) + handler: handleStaked + - event: Withdrawn(indexed address,uint256) + handler: handleWithdrawn + - event: RewardPaid(indexed address,uint256) + handler: handleRewardPaid + file: ./src/meow-staking.ts templates: - kind: ethereum name: Account From 56ab0de2ae0976f1d0c68b28b4fb20ecb95c4d92 Mon Sep 17 00:00:00 2001 From: tahos81 Date: Mon, 19 Aug 2024 12:33:07 +0300 Subject: [PATCH 030/103] feat: referral types --- subgraph/schema.graphql | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/subgraph/schema.graphql b/subgraph/schema.graphql index f4ee7d8..896b279 100644 --- a/subgraph/schema.graphql +++ b/subgraph/schema.graphql @@ -161,6 +161,23 @@ type ClaveAccount @entity { activeMonths: [MonthAccount!]! @derivedFrom(field: "account") } +type Cashback @entity { + "account.id.concat(erc20).concat('0xcb')" + id: Bytes! + account: ClaveAccount! + erc20: Bytes! + amount: BigInt! +} + +type ReferralFee @entity { + "account.id.refferred.id.concat(erc20)" + id: Bytes! + account: ClaveAccount! + referred: ClaveAccount! + erc20: Bytes! + amount: BigInt! +} + enum Paymaster { None ERC20 From 6572de5f9ff4823a33b13f626b2d04cf164b128c Mon Sep 17 00:00:00 2001 From: tahos81 Date: Mon, 19 Aug 2024 12:40:32 +0300 Subject: [PATCH 031/103] Update schema.graphql --- subgraph/schema.graphql | 2 ++ 1 file changed, 2 insertions(+) diff --git a/subgraph/schema.graphql b/subgraph/schema.graphql index 896b279..3340cee 100644 --- a/subgraph/schema.graphql +++ b/subgraph/schema.graphql @@ -159,6 +159,8 @@ type ClaveAccount @entity { activeDays: [DayAccount!]! @derivedFrom(field: "account") activeWeeks: [WeekAccount!]! @derivedFrom(field: "account") activeMonths: [MonthAccount!]! @derivedFrom(field: "account") + cashbacks: [Cashback!]! @derivedFrom(field: "account") + referralFees: [ReferralFee!]! @derivedFrom(field: "account") } type Cashback @entity { From d410f1084eeccb38c4b91dcd356d6cc21ee80eb2 Mon Sep 17 00:00:00 2001 From: tahos81 Date: Mon, 19 Aug 2024 12:40:36 +0300 Subject: [PATCH 032/103] Create SwapReferralFeePayer.json --- subgraph/abis/SwapReferralFeePayer.json | 80 +++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 subgraph/abis/SwapReferralFeePayer.json diff --git a/subgraph/abis/SwapReferralFeePayer.json b/subgraph/abis/SwapReferralFeePayer.json new file mode 100644 index 0000000..d72cb4b --- /dev/null +++ b/subgraph/abis/SwapReferralFeePayer.json @@ -0,0 +1,80 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "referred", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "fee", + "type": "uint256" + } + ], + "name": "Cashback", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "referrer", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "fee", + "type": "uint256" + } + ], + "name": "ReferralFee", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "referrer", + "type": "address" + }, + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "fee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "cashback", + "type": "uint256" + } + ], + "name": "payFee", + "outputs": [], + "stateMutability": "payable", + "type": "function" + } +] From 86b87265f9bfa4241837b94c11cc858836492f78 Mon Sep 17 00:00:00 2001 From: tahos81 Date: Mon, 19 Aug 2024 12:40:39 +0300 Subject: [PATCH 033/103] Update subgraph.yaml --- subgraph/subgraph.yaml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/subgraph/subgraph.yaml b/subgraph/subgraph.yaml index 25dc380..33ce2df 100644 --- a/subgraph/subgraph.yaml +++ b/subgraph/subgraph.yaml @@ -31,6 +31,29 @@ dataSources: - event: Claim(indexed address,indexed address,uint256,uint256) handler: handleClaim file: ./src/koi-pair.ts + - kind: ethereum + name: SwapReferralFeePayer + network: zksync-era + source: + address: tbd + abi: SwapReferralFeePayer + startBlock: tbd + mapping: + kind: ethereum/events + apiVersion: 0.0.7 + language: wasm/assemblyscript + entities: + - Cashback + - Referral + abis: + - name: SwapReferralFeePayer + file: ./abis/SwapReferralFeePayer.json + eventHandlers: + - event: ReferralFee(indexed address,indexed address,uint256) + handler: handleReferralFee + - event: Cashback(indexed address,indexed address,uint256) + handler: handleCashback + file: ./src/referral.ts - kind: ethereum name: OdosRouter network: zksync-era From db0a37bd2841a5734d65e734120deff607de99b1 Mon Sep 17 00:00:00 2001 From: tahos81 Date: Mon, 19 Aug 2024 12:59:52 +0300 Subject: [PATCH 034/103] Update helpers.ts --- subgraph/src/helpers.ts | 45 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/subgraph/src/helpers.ts b/subgraph/src/helpers.ts index c87a58c..66adad4 100644 --- a/subgraph/src/helpers.ts +++ b/subgraph/src/helpers.ts @@ -13,6 +13,7 @@ import { Bytes } from '@graphprotocol/graph-ts'; import { BigInt } from '@graphprotocol/graph-ts'; import { + Cashback, ClaveAccount, DailyEarnFlow, Day, @@ -21,6 +22,7 @@ import { Month, MonthAccount, MonthlyEarnFlow, + ReferralFee, Total, Week, WeekAccount, @@ -273,3 +275,46 @@ export function getOrCreateMonthlyEarnFlow( return monthlyEarnFlow; } + +export function getOrCreateCashback( + account: ClaveAccount, + token: Bytes, +): Cashback { + let cashbackId = account.id + .concat(token) + .concat(Bytes.fromHexString('0xcb')); + let cashback = Cashback.load(cashbackId); + + if (cashback !== null) { + return cashback; + } + + cashback = new Cashback(cashbackId); + cashback.account = account.id; + cashback.erc20 = token; + cashback.amount = ZERO; + + return cashback; +} + +export function getOrCreateReferralFee( + referrer: ClaveAccount, + referred: ClaveAccount, + token: Bytes, +): ReferralFee { + let referralFeeId = referrer.id.concat(referred.id).concat(token); + + let referralFee = ReferralFee.load(referralFeeId); + + if (referralFee !== null) { + return referralFee; + } + + referralFee = new ReferralFee(referralFeeId); + referralFee.account = referrer.id; + referralFee.referred = referred.id; + referralFee.erc20 = token; + referralFee.amount = ZERO; + + return referralFee; +} From 6ca7938cff90e49454060af9c40f9e57a269975a Mon Sep 17 00:00:00 2001 From: tahos81 Date: Mon, 19 Aug 2024 13:00:00 +0300 Subject: [PATCH 035/103] Create referral.ts --- subgraph/src/referral.ts | 42 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 subgraph/src/referral.ts diff --git a/subgraph/src/referral.ts b/subgraph/src/referral.ts new file mode 100644 index 0000000..b32c337 --- /dev/null +++ b/subgraph/src/referral.ts @@ -0,0 +1,42 @@ +/** + * Copyright Clave - All Rights Reserved + * Unauthorized copying of this file, via any medium is strictly prohibited + * Proprietary and confidential + */ +import type { + Cashback as CashbackEvent, + ReferralFee as ReferralFeeEvent, +} from '../generated/SwapReferralFeePayer/SwapReferralFeePayer'; +import { ClaveAccount } from '../generated/schema'; +import { getOrCreateCashback, getOrCreateReferralFee } from './helpers'; + +export function handleCashback(event: CashbackEvent): void { + const account = ClaveAccount.load(event.params.referred); + if (!account) { + return; + } + + const cashback = getOrCreateCashback(account, event.params.token); + cashback.amount = cashback.amount.plus(event.params.fee); + cashback.save(); +} + +export function handleReferralFee(event: ReferralFeeEvent): void { + const referrer = ClaveAccount.load(event.params.referrer); + if (!referrer) { + return; + } + + const referred = ClaveAccount.load(event.transaction.from); + if (!referred) { + return; + } + + const referralFee = getOrCreateReferralFee( + referrer, + referred, + event.params.token, + ); + referralFee.amount = referralFee.amount.plus(event.params.fee); + referralFee.save(); +} From 99185eeac1b730f693310c3c7a3d318d4356817f Mon Sep 17 00:00:00 2001 From: tahos81 Date: Wed, 21 Aug 2024 10:21:50 +0300 Subject: [PATCH 036/103] update --- subgraph/src/referral.ts | 4 +++- subgraph/subgraph.yaml | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/subgraph/src/referral.ts b/subgraph/src/referral.ts index b32c337..7a4d504 100644 --- a/subgraph/src/referral.ts +++ b/subgraph/src/referral.ts @@ -1,9 +1,11 @@ +/* eslint-disable @typescript-eslint/consistent-type-imports */ + /** * Copyright Clave - All Rights Reserved * Unauthorized copying of this file, via any medium is strictly prohibited * Proprietary and confidential */ -import type { +import { Cashback as CashbackEvent, ReferralFee as ReferralFeeEvent, } from '../generated/SwapReferralFeePayer/SwapReferralFeePayer'; diff --git a/subgraph/subgraph.yaml b/subgraph/subgraph.yaml index 33ce2df..1fe2351 100644 --- a/subgraph/subgraph.yaml +++ b/subgraph/subgraph.yaml @@ -1,6 +1,6 @@ specVersion: 1.0.0 indexerHints: - prune: auto + prune: 86400 schema: file: ./schema.graphql dataSources: @@ -35,9 +35,9 @@ dataSources: name: SwapReferralFeePayer network: zksync-era source: - address: tbd + address: '0x007966D09BF27c206c75B7048319dbf3a0852Df8' abi: SwapReferralFeePayer - startBlock: tbd + startBlock: 42221017 mapping: kind: ethereum/events apiVersion: 0.0.7 From 4a33d144c3a07cd84b3c9a8dbdfdd41165526909 Mon Sep 17 00:00:00 2001 From: asgarovf Date: Thu, 5 Sep 2024 11:24:54 +0300 Subject: [PATCH 037/103] [clave-landing] fix: improve preview amounts --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 47b0757..963bf8d 100644 --- a/package.json +++ b/package.json @@ -37,10 +37,10 @@ "license": "MIT", "main": "index.js", "scripts": { + "compile": "npx hardhat compile", + "deploy:mvp": "npx hardhat deploy-zksync --script deploy-mvp.ts", "lint": "npx prettier --write --plugin=prettier-plugin-solidity 'contracts/**/*.sol'", "lint-check": "npx prettier --list-different --plugin=prettier-plugin-solidity 'contracts/**/*.sol'", - "test": "TEST=true npx hardhat test --network hardhat", - "compile": "npx hardhat compile", - "deploy:mvp": "npx hardhat deploy-zksync --script deploy-mvp.ts" + "test": "TEST=true npx hardhat test --network hardhat" } } From af6ef7bcdefc73086cc452c50576d26ad5e0a3de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Tue, 10 Sep 2024 21:54:38 +0300 Subject: [PATCH 038/103] [clave-contracts] feat: deployer test class --- new-test/utils/deployer.ts | 210 +++++++++++++++++++++++++++++++++++++ new-test/utils/names.ts | 21 ++++ new-test/utils/p256.ts | 27 +++++ 3 files changed, 258 insertions(+) create mode 100644 new-test/utils/deployer.ts create mode 100644 new-test/utils/names.ts create mode 100644 new-test/utils/p256.ts diff --git a/new-test/utils/deployer.ts b/new-test/utils/deployer.ts new file mode 100644 index 0000000..25ab9b6 --- /dev/null +++ b/new-test/utils/deployer.ts @@ -0,0 +1,210 @@ +/** + * Copyright Clave - All Rights Reserved + * Unauthorized copying of this file, via any medium is strictly prohibited + * Proprietary and confidential + */ +import type { ec } from 'elliptic'; +import { AbiCoder, ZeroAddress, parseEther, randomBytes } from 'ethers'; +import type { HardhatRuntimeEnvironment } from 'hardhat/types'; +import type { Wallet } from 'zksync-ethers'; +import { Contract, utils } from 'zksync-ethers'; + +import { deployContract, getWallet } from '../../deploy/utils'; +import type { CallStruct } from '../../typechain-types/contracts/batch/BatchCaller'; +import { CONTRACT_NAMES, type VALIDATORS } from './names'; +import { encodePublicKey } from './p256'; + +// This class helps deploy Clave contracts for the tests +export class ClaveDeployer { + private hre: HardhatRuntimeEnvironment; + private deployerWallet: Wallet; + + constructor( + hre: HardhatRuntimeEnvironment, + deployerWallet: string | Wallet, + ) { + this.hre = hre; + + typeof deployerWallet === 'string' + ? (this.deployerWallet = getWallet(this.hre, deployerWallet)) + : (this.deployerWallet = deployerWallet); + } + + public async batchCaller(): Promise { + return await deployContract( + this.hre, + CONTRACT_NAMES.BATCH_CALLER, + undefined, + { + wallet: this.deployerWallet, + silent: true, + }, + ); + } + + public async registry(): Promise { + return await deployContract( + this.hre, + CONTRACT_NAMES.REGISTRY, + undefined, + { + wallet: this.deployerWallet, + silent: true, + }, + ); + } + + public async implementation(batchCaller: Contract): Promise { + return await deployContract( + this.hre, + CONTRACT_NAMES.IMPLEMENTATION, + [await batchCaller.getAddress()], + { + wallet: this.deployerWallet, + silent: true, + }, + ); + } + + public async factory( + implementation: Contract, + registry: Contract, + ): Promise { + // TODO: WHY DOES THIS HELP + await deployContract( + this.hre, + CONTRACT_NAMES.PROXY, + [await implementation.getAddress()], + { wallet: this.deployerWallet, silent: true }, + ); + + // Deploy factory contract + const accountArtifact = await this.hre.zksyncEthers.loadArtifact( + CONTRACT_NAMES.PROXY, + ); + const bytecodeHash = utils.hashBytecode(accountArtifact.bytecode); + const factory = await deployContract( + this.hre, + CONTRACT_NAMES.FACTORY, + [ + await implementation.getAddress(), + await registry.getAddress(), + bytecodeHash, + this.deployerWallet.address, + ], + { + wallet: this.deployerWallet, + silent: true, + }, + ); + + // Assign the factory address to the registry + const factorySetTx = await registry.setFactory( + await factory.getAddress(), + ); + await factorySetTx.wait(); + + return factory; + } + + public async setupFactory(): Promise<{ + batchCaller: Contract; + registry: Contract; + implementation: Contract; + factory: Contract; + }> { + const batchCaller = await this.batchCaller(); + const registry = await this.registry(); + const implementation = await this.implementation(batchCaller); + const factory = await this.factory(implementation, registry); + + return { batchCaller, registry, implementation, factory }; + } + + public async validator(name: VALIDATORS): Promise { + return await deployContract(this.hre, name, undefined, { + wallet: this.deployerWallet, + silent: true, + }); + } + + public async account( + keyPair: ec.KeyPair, + factory: Contract, + validator: Contract, + ): Promise { + const publicKey = encodePublicKey(keyPair); + + const salt = randomBytes(32); + const call: CallStruct = { + target: ZeroAddress, + allowFailure: false, + value: 0, + callData: '0x', + }; + + const abiCoder = AbiCoder.defaultAbiCoder(); + const initializer = + '0x77ba2e75' + + abiCoder + .encode( + [ + 'bytes', + 'address', + 'bytes[]', + 'tuple(address target,bool allowFailure,uint256 value,bytes calldata)', + ], + [ + publicKey, + await validator.getAddress(), + [], + [ + call.target, + call.allowFailure, + call.value, + call.callData, + ], + ], + ) + .slice(2); + + const deployPromise = await Promise.all([ + // Deploy account + (async (): Promise => { + const deployTx = await factory.deployAccount(salt, initializer); + await deployTx.wait(); + })(), + // Calculate new account address + (async (): Promise => { + return await factory.getAddressForSalt(salt); + })(), + ]); + + const accountAddress = deployPromise[1]; + const implementationInterface = ( + await this.hre.zksyncEthers.getContractFactory( + CONTRACT_NAMES.IMPLEMENTATION, + ) + ).interface; + + const account = new Contract( + accountAddress, + implementationInterface, + this.deployerWallet, + ); + + return account; + } + + public async fund( + ethAmount: number, + accountAddress: string, + ): Promise { + await ( + await this.deployerWallet.sendTransaction({ + to: accountAddress, + value: parseEther(ethAmount.toString()), + }) + ).wait(); + } +} diff --git a/new-test/utils/names.ts b/new-test/utils/names.ts new file mode 100644 index 0000000..f7b32e4 --- /dev/null +++ b/new-test/utils/names.ts @@ -0,0 +1,21 @@ +/** + * Copyright Clave - All Rights Reserved + * Unauthorized copying of this file, via any medium is strictly prohibited + * Proprietary and confidential + */ + +export const CONTRACT_NAMES = { + BATCH_CALLER: 'BatchCaller', + REGISTRY: 'ClaveRegistry', + IMPLEMENTATION: 'ClaveImplementation', + PROXY: 'ClaveProxy', + FACTORY: 'AccountFactory', + MOCK_VALIDATOR: 'MockValidator', +}; + +export enum VALIDATORS { + MOCK = 'MockValidator', + TEE = 'TeeValidator', + EOA = 'EOAValidator', + PASSKEY = 'PasskeyValidator', +} diff --git a/new-test/utils/p256.ts b/new-test/utils/p256.ts new file mode 100644 index 0000000..4e187f7 --- /dev/null +++ b/new-test/utils/p256.ts @@ -0,0 +1,27 @@ +/** + * Copyright Clave - All Rights Reserved + * Unauthorized copying of this file, via any medium is strictly prohibited + * Proprietary and confidential + */ +import elliptic from 'elliptic'; + +export function genKey(): elliptic.ec.KeyPair { + const ec = new elliptic.ec('p256'); + return ec.genKeyPair(); +} + +export function encodePublicKey(key: elliptic.ec.KeyPair): string { + const pubKey = key.getPublic(); + const x = pubKey.getX().toString('hex').padStart(64, '0'); + const y = pubKey.getY().toString('hex').padStart(64, '0'); + + return '0x' + x + y; +} + +export function sign(msg: string, key: elliptic.ec.KeyPair): string { + const buffer = Buffer.from(msg.slice(2), 'hex'); + const signature = key.sign(buffer); + const r = signature.r.toString('hex').padStart(64, '0'); + const s = signature.s.toString('hex').padStart(64, '0'); + return '0x' + r + s; +} From 282b19fab57c7116188bb771e5f554e70ae2faf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Tue, 10 Sep 2024 21:54:55 +0300 Subject: [PATCH 039/103] [clave-contracts] feat: deployer class testing initiation --- new-test/deployer.test.ts | 113 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 new-test/deployer.test.ts diff --git a/new-test/deployer.test.ts b/new-test/deployer.test.ts new file mode 100644 index 0000000..7c5be9d --- /dev/null +++ b/new-test/deployer.test.ts @@ -0,0 +1,113 @@ +import { expect } from 'chai'; +import type { ec } from 'elliptic'; +import type { BytesLike } from 'ethers'; +import { parseEther } from 'ethers'; +import * as hre from 'hardhat'; +import type { Contract, Wallet } from 'zksync-ethers'; +import { Provider } from 'zksync-ethers'; + +import { LOCAL_RICH_WALLETS, getWallet } from '../deploy/utils'; +import { ClaveDeployer } from './utils/deployer'; +import { VALIDATORS } from './utils/names'; +import { encodePublicKey, genKey } from './utils/p256'; + +/** + * Copyright Clave - All Rights Reserved + * Unauthorized copying of this file, via any medium is strictly prohibited + * Proprietary and confidential + */ + +describe('Clave Contracts - Deployer class tests', () => { + let deployer: ClaveDeployer; + let provider: Provider; + let richWallet: Wallet; + let batchCaller: Contract; + let registry: Contract; + let implementation: Contract; + let factory: Contract; + let mockValidator: Contract; + let account: Contract; + let keyPair: ec.KeyPair; + + before(async () => { + richWallet = getWallet(hre, LOCAL_RICH_WALLETS[0].privateKey); + deployer = new ClaveDeployer(hre, richWallet); + + provider = new Provider(hre.network.config.url, undefined, { + cacheTimeout: -1, + }); + + batchCaller = await deployer.batchCaller(); + registry = await deployer.registry(); + implementation = await deployer.implementation(batchCaller); + factory = await deployer.factory(implementation, registry); + mockValidator = await deployer.validator(VALIDATORS.MOCK); + + keyPair = genKey(); + account = await deployer.account(keyPair, factory, mockValidator); + + await deployer.fund(100, await account.getAddress()); + }); + + describe('Contracts', () => { + it('should deploy the contracts', async () => { + const batchCallerAddress = await batchCaller.getAddress(); + expect(batchCallerAddress).not.to.be.undefined; + const registryAddress = await registry.getAddress(); + expect(registryAddress).not.to.be.undefined; + const implementationAddress = await implementation.getAddress(); + expect(implementationAddress).not.to.be.undefined; + const factoryAddress = await factory.getAddress(); + expect(factoryAddress).not.to.be.undefined; + const mockValidatorAddress = await mockValidator.getAddress(); + expect(mockValidatorAddress).not.to.be.undefined; + const accountAddress = await account.getAddress(); + expect(accountAddress).not.to.be.undefined; + }); + }); + + describe('States', () => { + it('should fund the account', async () => { + const balance = await provider.getBalance( + await account.getAddress(), + ); + expect(balance).to.eq(parseEther('100')); + }); + + it('account keeps correct states', async () => { + const validatorAddress = await mockValidator.getAddress(); + const implementationAddress = await implementation.getAddress(); + + const expectedR1Validators = [validatorAddress]; + const expectedK1Validators: Array = []; + const expectedR1Owners = [encodePublicKey(keyPair)]; + const expectedK1Owners: Array = []; + const expectedModules: Array = []; + const expectedHooks: Array = []; + const expectedImplementation = implementationAddress; + + expect(await account.r1ListValidators()).to.deep.eq( + expectedR1Validators, + ); + expect(await account.k1ListValidators()).to.deep.eq( + expectedK1Validators, + ); + expect(await account.r1ListOwners()).to.deep.eq(expectedR1Owners); + expect(await account.k1ListOwners()).to.deep.eq(expectedK1Owners); + expect(await account.listModules()).to.deep.eq(expectedModules); + expect(await account.listHooks(false)).to.deep.eq(expectedHooks); + expect(await account.listHooks(true)).to.deep.eq(expectedHooks); + expect(await account.implementation()).to.eq( + expectedImplementation, + ); + }); + + it('registry is deployed and states are expected', async function () { + const accountAddress = await account.getAddress(); + const factoryAddress = await factory.getAddress(); + + expect(await registry.isClave(accountAddress)).to.be.true; + expect(await registry.isClave(factoryAddress)).not.to.be.true; + }); + }); +}); From 704d2a1d2b471dcc8a75ca77c1b000a5b1a7d7c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Tue, 10 Sep 2024 22:36:32 +0300 Subject: [PATCH 040/103] [clave-contracts] refactor: wrong comment order --- new-test/deployer.test.ts | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/new-test/deployer.test.ts b/new-test/deployer.test.ts index 7c5be9d..c01cff1 100644 --- a/new-test/deployer.test.ts +++ b/new-test/deployer.test.ts @@ -1,3 +1,8 @@ +/** + * Copyright Clave - All Rights Reserved + * Unauthorized copying of this file, via any medium is strictly prohibited + * Proprietary and confidential + */ import { expect } from 'chai'; import type { ec } from 'elliptic'; import type { BytesLike } from 'ethers'; @@ -11,12 +16,6 @@ import { ClaveDeployer } from './utils/deployer'; import { VALIDATORS } from './utils/names'; import { encodePublicKey, genKey } from './utils/p256'; -/** - * Copyright Clave - All Rights Reserved - * Unauthorized copying of this file, via any medium is strictly prohibited - * Proprietary and confidential - */ - describe('Clave Contracts - Deployer class tests', () => { let deployer: ClaveDeployer; let provider: Provider; @@ -51,18 +50,12 @@ describe('Clave Contracts - Deployer class tests', () => { describe('Contracts', () => { it('should deploy the contracts', async () => { - const batchCallerAddress = await batchCaller.getAddress(); - expect(batchCallerAddress).not.to.be.undefined; - const registryAddress = await registry.getAddress(); - expect(registryAddress).not.to.be.undefined; - const implementationAddress = await implementation.getAddress(); - expect(implementationAddress).not.to.be.undefined; - const factoryAddress = await factory.getAddress(); - expect(factoryAddress).not.to.be.undefined; - const mockValidatorAddress = await mockValidator.getAddress(); - expect(mockValidatorAddress).not.to.be.undefined; - const accountAddress = await account.getAddress(); - expect(accountAddress).not.to.be.undefined; + expect(await batchCaller.getAddress()).not.to.be.undefined; + expect(await registry.getAddress()).not.to.be.undefined; + expect(await implementation.getAddress()).not.to.be.undefined; + expect(await factory.getAddress()).not.to.be.undefined; + expect(await mockValidator.getAddress()).not.to.be.undefined; + expect(await account.getAddress()).not.to.be.undefined; }); }); From a3e17c771fba07ff544e999f8a49754600f36422 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Tue, 10 Sep 2024 22:56:33 +0300 Subject: [PATCH 041/103] [clave-contracts] feat: setup fixture for deployments --- new-test/deployer.test.ts | 22 ++++++++++---------- new-test/utils/fixture.ts | 44 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 11 deletions(-) create mode 100644 new-test/utils/fixture.ts diff --git a/new-test/deployer.test.ts b/new-test/deployer.test.ts index c01cff1..0436ab7 100644 --- a/new-test/deployer.test.ts +++ b/new-test/deployer.test.ts @@ -13,8 +13,8 @@ import { Provider } from 'zksync-ethers'; import { LOCAL_RICH_WALLETS, getWallet } from '../deploy/utils'; import { ClaveDeployer } from './utils/deployer'; -import { VALIDATORS } from './utils/names'; -import { encodePublicKey, genKey } from './utils/p256'; +import { fixture } from './utils/fixture'; +import { encodePublicKey } from './utils/p256'; describe('Clave Contracts - Deployer class tests', () => { let deployer: ClaveDeployer; @@ -31,19 +31,19 @@ describe('Clave Contracts - Deployer class tests', () => { before(async () => { richWallet = getWallet(hre, LOCAL_RICH_WALLETS[0].privateKey); deployer = new ClaveDeployer(hre, richWallet); - provider = new Provider(hre.network.config.url, undefined, { cacheTimeout: -1, }); - batchCaller = await deployer.batchCaller(); - registry = await deployer.registry(); - implementation = await deployer.implementation(batchCaller); - factory = await deployer.factory(implementation, registry); - mockValidator = await deployer.validator(VALIDATORS.MOCK); - - keyPair = genKey(); - account = await deployer.account(keyPair, factory, mockValidator); + [ + batchCaller, + registry, + implementation, + factory, + mockValidator, + account, + keyPair, + ] = await fixture(deployer); await deployer.fund(100, await account.getAddress()); }); diff --git a/new-test/utils/fixture.ts b/new-test/utils/fixture.ts new file mode 100644 index 0000000..e276507 --- /dev/null +++ b/new-test/utils/fixture.ts @@ -0,0 +1,44 @@ +/** + * Copyright Clave - All Rights Reserved + * Unauthorized copying of this file, via any medium is strictly prohibited + * Proprietary and confidential + */ +import type { ec } from 'elliptic'; +import type { Contract } from 'zksync-ethers'; + +import type { ClaveDeployer } from './deployer'; +import { VALIDATORS } from './names'; +import { genKey } from './p256'; + +export type fixtureTypes = [ + batchCaller: Contract, + registry: Contract, + implementation: Contract, + factory: Contract, + mockValidator: Contract, + account: Contract, + keyPair: ec.KeyPair, +]; + +export const fixture = async ( + deployer: ClaveDeployer, +): Promise => { + const keyPair = genKey(); + + const batchCaller = await deployer.batchCaller(); + const registry = await deployer.registry(); + const implementation = await deployer.implementation(batchCaller); + const factory = await deployer.factory(implementation, registry); + const mockValidator = await deployer.validator(VALIDATORS.MOCK); + const account = await deployer.account(keyPair, factory, mockValidator); + + return [ + batchCaller, + registry, + implementation, + factory, + mockValidator, + account, + keyPair, + ]; +}; From 3a422f00e93f4438ac50e38d4900f305593a8506 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Wed, 11 Sep 2024 15:23:03 +0300 Subject: [PATCH 042/103] [clave-contracts] refactor: file formats --- new-test/accounts/transactions.ts | 5 +++++ new-test/{ => deployments}/deployer.test.ts | 8 ++++---- 2 files changed, 9 insertions(+), 4 deletions(-) create mode 100644 new-test/accounts/transactions.ts rename new-test/{ => deployments}/deployer.test.ts (94%) diff --git a/new-test/accounts/transactions.ts b/new-test/accounts/transactions.ts new file mode 100644 index 0000000..4ddd6b2 --- /dev/null +++ b/new-test/accounts/transactions.ts @@ -0,0 +1,5 @@ +/** + * Copyright Clave - All Rights Reserved + * Unauthorized copying of this file, via any medium is strictly prohibited + * Proprietary and confidential + */ diff --git a/new-test/deployer.test.ts b/new-test/deployments/deployer.test.ts similarity index 94% rename from new-test/deployer.test.ts rename to new-test/deployments/deployer.test.ts index 0436ab7..684b1a7 100644 --- a/new-test/deployer.test.ts +++ b/new-test/deployments/deployer.test.ts @@ -11,10 +11,10 @@ import * as hre from 'hardhat'; import type { Contract, Wallet } from 'zksync-ethers'; import { Provider } from 'zksync-ethers'; -import { LOCAL_RICH_WALLETS, getWallet } from '../deploy/utils'; -import { ClaveDeployer } from './utils/deployer'; -import { fixture } from './utils/fixture'; -import { encodePublicKey } from './utils/p256'; +import { LOCAL_RICH_WALLETS, getWallet } from '../../deploy/utils'; +import { ClaveDeployer } from '../utils/deployer'; +import { fixture } from '../utils/fixture'; +import { encodePublicKey } from '../utils/p256'; describe('Clave Contracts - Deployer class tests', () => { let deployer: ClaveDeployer; From 86b84db7f6835987aef78677931f9bb9f582edc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Wed, 11 Sep 2024 17:19:49 +0300 Subject: [PATCH 043/103] [clave-contracts] feat: implement tx types tests --- new-test/accounts/transactions.test.ts | 214 ++++++++++++++++++++++++ new-test/accounts/transactions.ts | 5 - new-test/utils/transactions.ts | 216 +++++++++++++++++++++++++ 3 files changed, 430 insertions(+), 5 deletions(-) create mode 100644 new-test/accounts/transactions.test.ts delete mode 100644 new-test/accounts/transactions.ts create mode 100644 new-test/utils/transactions.ts diff --git a/new-test/accounts/transactions.test.ts b/new-test/accounts/transactions.test.ts new file mode 100644 index 0000000..1892fc8 --- /dev/null +++ b/new-test/accounts/transactions.test.ts @@ -0,0 +1,214 @@ +/** + * Copyright Clave - All Rights Reserved + * Unauthorized copying of this file, via any medium is strictly prohibited + * Proprietary and confidential + */ +import { expect } from 'chai'; +import { parseEther } from 'ethers'; +import * as hre from 'hardhat'; +import type { Contract, Wallet } from 'zksync-ethers'; +import { Provider, utils } from 'zksync-ethers'; + +import { LOCAL_RICH_WALLETS, getWallet } from '../../deploy/utils'; +import type { CallStruct } from '../../typechain-types/contracts/batch/BatchCaller'; +import { ClaveDeployer } from '../utils/deployer'; +import { fixture } from '../utils/fixture'; +import { + ethTransfer, + prepareMockBatchTx, + prepareMockTx, +} from '../utils/transactions'; + +describe('Clave Contracts - Account tests', () => { + let deployer: ClaveDeployer; + let provider: Provider; + let richWallet: Wallet; + let batchCaller: Contract; + let mockValidator: Contract; + let account: Contract; + + let erc20: Contract; + + before(async () => { + richWallet = getWallet(hre, LOCAL_RICH_WALLETS[0].privateKey); + deployer = new ClaveDeployer(hre, richWallet); + provider = new Provider(hre.network.config.url, undefined, { + cacheTimeout: -1, + }); + + [batchCaller, , , , mockValidator, account] = await fixture(deployer); + + const accountAddress = await account.getAddress(); + + await deployer.fund(10000, accountAddress); + + erc20 = await deployer.deployCustomContract('MockStable', []); + await erc20.mint(accountAddress, parseEther('100000')); + }); + + describe('Transactions', () => { + let accountAddress: string; + let richAddress: string; + let accountBalanceBefore: bigint; + let richBalanceBefore: bigint; + + before(async () => { + const addresses = await Promise.all([ + account.getAddress(), + richWallet.getAddress(), + ]); + [accountAddress, richAddress] = addresses; + }); + + beforeEach(async () => { + const balances = await Promise.all([ + provider.getBalance(accountAddress), + provider.getBalance(richAddress), + ]); + [accountBalanceBefore, richBalanceBefore] = balances; + }); + + it('should send ETH', async () => { + const amount = parseEther('1'); + const delta = parseEther('0.01'); + + const txData = ethTransfer(richAddress, amount); + const tx = await prepareMockTx( + provider, + account, + txData, + await mockValidator.getAddress(), + ); + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + await txReceipt.wait(); + + const [accountBalanceAfter, richBalanceAfter] = await Promise.all([ + provider.getBalance(accountAddress), + provider.getBalance(richAddress), + ]); + + expect(accountBalanceAfter).to.be.closeTo( + accountBalanceBefore - amount, + delta, + ); + expect(richBalanceAfter).to.be.equal(richBalanceBefore + amount); + }); + + it('should send ERC20 token / contract interaction and pay gas', async () => { + const amount = parseEther('100'); + + const [accountERC20BalanceBefore, richERC20BalanceBefore] = + await Promise.all([ + erc20.balanceOf(accountAddress), + erc20.balanceOf(richAddress), + ]); + + const txData = { + to: await erc20.getAddress(), + value: 0, + data: erc20.interface.encodeFunctionData('transfer', [ + richAddress, + amount, + ]), + }; + const tx = await prepareMockTx( + provider, + account, + txData, + await mockValidator.getAddress(), + ); + + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + await txReceipt.wait(); + + const [accountERC20BalanceAfter, richERC20BalanceAfter] = + await Promise.all([ + erc20.balanceOf(accountAddress), + erc20.balanceOf(richAddress), + ]); + + expect(accountERC20BalanceAfter).to.be.equal( + accountERC20BalanceBefore - amount, + ); + expect(richERC20BalanceAfter).to.be.equal( + richERC20BalanceBefore + amount, + ); + + // Gas payment check + const accountBalanceAfter = await provider.getBalance( + accountAddress, + ); + expect(accountBalanceBefore).to.be.greaterThan(accountBalanceAfter); + }); + + it('should send batch tx / delegate call', async () => { + const amount = parseEther('100'); + const delta = parseEther('0.01'); + + const [accountERC20BalanceBefore, richERC20BalanceBefore] = + await Promise.all([ + erc20.balanceOf(accountAddress), + erc20.balanceOf(richAddress), + ]); + + const calls: Array = [ + { + target: richAddress, + allowFailure: false, + value: amount, + callData: '0x', + }, + { + target: await erc20.getAddress(), + allowFailure: false, + value: 0, + callData: erc20.interface.encodeFunctionData('transfer', [ + richAddress, + amount, + ]), + }, + ]; + + const batchTx = await prepareMockBatchTx( + provider, + account, + await batchCaller.getAddress(), + calls, + await mockValidator.getAddress(), + ); + + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(batchTx), + ); + await txReceipt.wait(); + + const [accountBalanceAfter, richBalanceAfter] = await Promise.all([ + provider.getBalance(accountAddress), + provider.getBalance(richAddress), + ]); + + expect(accountBalanceAfter).to.be.closeTo( + accountBalanceBefore - amount, + delta, + ); + expect(richBalanceAfter).to.be.equal(richBalanceBefore + amount); + + const [accountERC20BalanceAfter, richERC20BalanceAfter] = + await Promise.all([ + erc20.balanceOf(accountAddress), + erc20.balanceOf(richAddress), + ]); + + expect(accountERC20BalanceAfter).to.be.equal( + accountERC20BalanceBefore - amount, + ); + expect(richERC20BalanceAfter).to.be.equal( + richERC20BalanceBefore + amount, + ); + }); + }); +}); diff --git a/new-test/accounts/transactions.ts b/new-test/accounts/transactions.ts deleted file mode 100644 index 4ddd6b2..0000000 --- a/new-test/accounts/transactions.ts +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Copyright Clave - All Rights Reserved - * Unauthorized copying of this file, via any medium is strictly prohibited - * Proprietary and confidential - */ diff --git a/new-test/utils/transactions.ts b/new-test/utils/transactions.ts new file mode 100644 index 0000000..3290971 --- /dev/null +++ b/new-test/utils/transactions.ts @@ -0,0 +1,216 @@ +/** + * Copyright Clave - All Rights Reserved + * Unauthorized copying of this file, via any medium is strictly prohibited + * Proprietary and confidential + */ +import type { ec } from 'elliptic'; +import type { BigNumberish } from 'ethers'; +import { ethers, parseEther } from 'ethers'; +import type { Contract, Provider, types } from 'zksync-ethers'; +import { EIP712Signer, utils } from 'zksync-ethers'; + +import type { CallStruct } from '../../typechain-types/contracts/batch/BatchCaller'; +import { sign } from './p256'; + +export const ethTransfer = ( + to: string, + value: BigNumberish, +): types.TransactionLike => { + return { + to, + value, + data: '0x', + }; +}; + +export async function prepareMockTx( + provider: Provider, + account: Contract, + tx: types.TransactionLike, + validatorAddress: string, + paymasterParams?: types.PaymasterParams, +): Promise { + const abiCoder = ethers.AbiCoder.defaultAbiCoder(); + const signature = abiCoder.encode( + ['bytes', 'address', 'bytes[]'], + ['0x' + 'C1AE'.repeat(32), validatorAddress, []], + ); + + tx = { + ...tx, + from: await account.getAddress(), + nonce: await provider.getTransactionCount(await account.getAddress()), + gasLimit: 10_000_000, + gasPrice: await provider.getGasPrice(), + chainId: (await provider.getNetwork()).chainId, + type: 113, + customData: { + gasPerPubdata: utils.DEFAULT_GAS_PER_PUBDATA_LIMIT, + customSignature: signature, + paymasterParams: paymasterParams, + } as types.Eip712Meta, + }; + + return tx; +} + +export async function prepareMockBatchTx( + provider: Provider, + account: Contract, + BatchCallerAddress: string, + calls: Array, + validatorAddress: string, + hookData: Array = [], + paymasterParams?: types.PaymasterParams, +): Promise { + const abiCoder = ethers.AbiCoder.defaultAbiCoder(); + + const data = + '0x8f0273a9' + + abiCoder + .encode( + [ + 'tuple(address target, bool allowFailure, uint256 value, bytes callData)[]', + ], + [calls], + ) + .slice(2); + + let totalValue: BigNumberish = '0'; + for (const call of calls) { + totalValue += call.value; + } + + const tx = { + to: BatchCallerAddress, + from: await account.getAddress(), + nonce: await provider.getTransactionCount(await account.getAddress()), + gasLimit: 30_000_000, + gasPrice: await provider.getGasPrice(), + data, + value: totalValue, + chainId: (await provider.getNetwork()).chainId, + type: 113, + customData: { + gasPerPubdata: utils.DEFAULT_GAS_PER_PUBDATA_LIMIT, + paymasterParams, + } as types.Eip712Meta, + }; + + const signature = abiCoder.encode( + ['bytes', 'address', 'bytes[]'], + ['0x' + 'C1AE'.repeat(32), validatorAddress, hookData], + ); + + tx.customData = { + ...tx.customData, + customSignature: signature, + }; + + return tx; +} + +export async function prepareTeeTx( + provider: Provider, + account: Contract, + tx: types.TransactionLike, + validatorAddress: string, + keyPair: ec.KeyPair, + hookData: Array = [], + paymasterParams?: types.PaymasterParams, +): Promise { + if (tx.value == undefined) { + tx.value = parseEther('0'); + } + + tx = { + ...tx, + from: await account.getAddress(), + nonce: await provider.getTransactionCount(await account.getAddress()), + gasLimit: 30_000_000, + gasPrice: await provider.getGasPrice(), + chainId: (await provider.getNetwork()).chainId, + type: 113, + customData: { + gasPerPubdata: utils.DEFAULT_GAS_PER_PUBDATA_LIMIT, + paymasterParams: paymasterParams, + } as types.Eip712Meta, + }; + + const signedTxHash = EIP712Signer.getSignedDigest(tx); + + const abiCoder = ethers.AbiCoder.defaultAbiCoder(); + let signature = sign(signedTxHash.toString(), keyPair); + + signature = abiCoder.encode( + ['bytes', 'address', 'bytes[]'], + [signature, validatorAddress, hookData], + ); + + tx.customData = { + ...tx.customData, + customSignature: signature, + }; + + return tx; +} + +export async function prepareBatchTx( + provider: Provider, + account: Contract, + BatchCallerAddress: string, + calls: Array, + validatorAddress: string, + keyPair: ec.KeyPair, + hookData: Array = [], + paymasterParams?: types.PaymasterParams, +): Promise { + const abiCoder = ethers.AbiCoder.defaultAbiCoder(); + const data = + '0x8f0273a9' + + abiCoder + .encode( + [ + 'tuple(address target, bool allowFailure, uint256 value, bytes callData)[]', + ], + [calls], + ) + .slice(2); + + let totalValue: BigNumberish = '0'; + for (const call of calls) { + totalValue += call.value; + } + + const tx = { + to: BatchCallerAddress, + from: await account.getAddress(), + nonce: await provider.getTransactionCount(await account.getAddress()), + gasLimit: 30_000_000, + gasPrice: await provider.getGasPrice(), + data, + value: totalValue, + chainId: (await provider.getNetwork()).chainId, + type: 113, + customData: { + gasPerPubdata: utils.DEFAULT_GAS_PER_PUBDATA_LIMIT, + paymasterParams, + } as types.Eip712Meta, + }; + + const signedTxHash = EIP712Signer.getSignedDigest(tx); + + let signature = sign(signedTxHash.toString(), keyPair); + + signature = abiCoder.encode( + ['bytes', 'address', 'bytes[]'], + [signature, validatorAddress, hookData], + ); + + tx.customData = { + ...tx.customData, + customSignature: signature, + }; + + return tx; +} From f53d347a4da54e5aff99cab71bf4d17a05863cac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Wed, 11 Sep 2024 17:20:01 +0300 Subject: [PATCH 044/103] [clave-contracts] feat: allow custom deployments by deployer class --- new-test/utils/deployer.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/new-test/utils/deployer.ts b/new-test/utils/deployer.ts index 25ab9b6..6322e1a 100644 --- a/new-test/utils/deployer.ts +++ b/new-test/utils/deployer.ts @@ -207,4 +207,14 @@ export class ClaveDeployer { }) ).wait(); } + + public async deployCustomContract( + name: string, + constructorArgs: Array, + ): Promise { + return await deployContract(this.hre, name, constructorArgs, { + wallet: this.deployerWallet, + silent: true, + }); + } } From b8cc5d617c282c5f585f4565b92b0be137ce302a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Thu, 12 Sep 2024 05:10:54 +0300 Subject: [PATCH 045/103] [clave-contracts] feat: enable rip-7212 in hardhat settings --- contracts/validators/TEEValidator.sol | 4 ++-- hardhat.config.ts | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/contracts/validators/TEEValidator.sol b/contracts/validators/TEEValidator.sol index 87343e9..eee5070 100644 --- a/contracts/validators/TEEValidator.sol +++ b/contracts/validators/TEEValidator.sol @@ -10,8 +10,8 @@ import {VerifierCaller} from '../helpers/VerifierCaller.sol'; * @author https://getclave.io */ contract TEEValidator is IR1Validator, VerifierCaller { - //dummy value - address constant P256_VERIFIER = 0x4323cffC1Fda2da9928cB5A5A9dA45DC8Ee38a2f; + // RIP-7212 enabled + address constant P256_VERIFIER = 0x0000000000000000000000000000000000000100; /// @inheritdoc IR1Validator function validateSignature( diff --git a/hardhat.config.ts b/hardhat.config.ts index 33e318c..ce681bb 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -22,6 +22,7 @@ const zkSyncMainnet: NetworkUserConfig = { verifyURL: 'https://zksync2-mainnet-explorer.zksync.io/contract_verification', chainId: 324, + enableRip7212: true, }; const zkSyncSepolia: NetworkUserConfig = { @@ -30,6 +31,7 @@ const zkSyncSepolia: NetworkUserConfig = { zksync: true, verifyURL: 'https://explorer.sepolia.era.zksync.dev/contract_verification', chainId: 300, + enableRip7212: true, }; const inMemoryNode: NetworkUserConfig = { @@ -37,6 +39,7 @@ const inMemoryNode: NetworkUserConfig = { ethNetwork: '', // in-memory node doesn't support eth node; removing this line will cause an error zksync: true, chainId: 260, + enableRip7212: true, }; const dockerizedNode: NetworkUserConfig = { @@ -44,6 +47,7 @@ const dockerizedNode: NetworkUserConfig = { ethNetwork: 'http://localhost:8545', zksync: true, chainId: 270, + enableRip7212: true, }; const config: HardhatUserConfig = { From 4bb00077b5d4549e01e852d289cb86de4e595cd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Thu, 12 Sep 2024 05:21:56 +0300 Subject: [PATCH 046/103] [clave-contracts] feat: rip-7212 tests --- hardhat.config.ts | 1 + new-test/deployments/rip7212.test.ts | 40 ++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 new-test/deployments/rip7212.test.ts diff --git a/hardhat.config.ts b/hardhat.config.ts index ce681bb..0488eaa 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -71,6 +71,7 @@ const config: HardhatUserConfig = { networks: { hardhat: { zksync: true, + enableRip7212: true, }, zkSyncSepolia, zkSyncMainnet, diff --git a/new-test/deployments/rip7212.test.ts b/new-test/deployments/rip7212.test.ts new file mode 100644 index 0000000..94278ea --- /dev/null +++ b/new-test/deployments/rip7212.test.ts @@ -0,0 +1,40 @@ +/** + * Copyright Clave - All Rights Reserved + * Unauthorized copying of this file, via any medium is strictly prohibited + * Proprietary and confidential + */ + +/** + * Copyright Clave - All Rights Reserved + * Unauthorized copying of this file, via any medium is strictly prohibited + * Proprietary and confidential + */ +import { expect } from 'chai'; +import * as hre from 'hardhat'; +import { Provider } from 'zksync-ethers'; + +describe('RIP-7212 tests', () => { + let provider: Provider; + + before(async () => { + provider = new Provider(hre.network.config.url, undefined, { + cacheTimeout: -1, + }); + }); + + it('should enable rip-7212', async () => { + const precompileAddress = '0x0000000000000000000000000000000000000100'; + const data = + '0x4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e'; + + const result = await provider.call({ + to: precompileAddress, + data: data, + value: 0, + }); + + expect(result).to.be.eq( + '0x0000000000000000000000000000000000000000000000000000000000000001', + ); + }); +}); From 542e9b2302ef4fa49db2bb2890d8757776107ce4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Thu, 12 Sep 2024 05:27:48 +0300 Subject: [PATCH 047/103] [clave-contracts] fix: double copyright --- new-test/deployments/rip7212.test.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/new-test/deployments/rip7212.test.ts b/new-test/deployments/rip7212.test.ts index 94278ea..5f70c9b 100644 --- a/new-test/deployments/rip7212.test.ts +++ b/new-test/deployments/rip7212.test.ts @@ -1,9 +1,3 @@ -/** - * Copyright Clave - All Rights Reserved - * Unauthorized copying of this file, via any medium is strictly prohibited - * Proprietary and confidential - */ - /** * Copyright Clave - All Rights Reserved * Unauthorized copying of this file, via any medium is strictly prohibited From 1dd169ed40dc084023971364b7aa743fd53cd320 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Thu, 12 Sep 2024 05:42:43 +0300 Subject: [PATCH 048/103] [clave-contracts] feat: owner manager tests, not-done --- .../accounts/managers/ownermanager.test.ts | 115 ++++++++++++++++++ new-test/utils/fixture.ts | 7 +- new-test/utils/names.ts | 2 +- 3 files changed, 120 insertions(+), 4 deletions(-) create mode 100644 new-test/accounts/managers/ownermanager.test.ts diff --git a/new-test/accounts/managers/ownermanager.test.ts b/new-test/accounts/managers/ownermanager.test.ts new file mode 100644 index 0000000..d2c381e --- /dev/null +++ b/new-test/accounts/managers/ownermanager.test.ts @@ -0,0 +1,115 @@ +/** + * Copyright Clave - All Rights Reserved + * Unauthorized copying of this file, via any medium is strictly prohibited + * Proprietary and confidential + */ +import { expect } from 'chai'; +import type { ec } from 'elliptic'; +import { parseEther } from 'ethers'; +import * as hre from 'hardhat'; +import type { Contract, Wallet } from 'zksync-ethers'; +import { Provider, utils } from 'zksync-ethers'; + +import { LOCAL_RICH_WALLETS, getWallet } from '../../../deploy/utils'; +import { ClaveDeployer } from '../../utils/deployer'; +import { fixture } from '../../utils/fixture'; +import { VALIDATORS } from '../../utils/names'; +import { encodePublicKey, genKey } from '../../utils/p256'; +import { prepareTeeTx } from '../../utils/transactions'; + +describe('Clave Contracts - Manager tests', () => { + let deployer: ClaveDeployer; + let provider: Provider; + let richWallet: Wallet; + let validator: Contract; + let account: Contract; + let keyPair: ec.KeyPair; + + let erc20: Contract; + + before(async () => { + richWallet = getWallet(hre, LOCAL_RICH_WALLETS[0].privateKey); + deployer = new ClaveDeployer(hre, richWallet); + provider = new Provider(hre.network.config.url, undefined, { + cacheTimeout: -1, + }); + + [, , , , validator, account, keyPair] = await fixture( + deployer, + // VALIDATORS.TEE, // FIXME: non-Mock validators not working + ); + + const accountAddress = await account.getAddress(); + + await deployer.fund(10000, accountAddress); + + erc20 = await deployer.deployCustomContract('MockStable', []); + await erc20.mint(accountAddress, parseEther('100000')); + }); + + describe('Owner Manager', () => { + it('should check existing key', async () => { + const newPublicKey = encodePublicKey(keyPair); + expect(await account.r1IsOwner(newPublicKey)).to.be.true; + }); + + let newKeyPair: ec.KeyPair; + let newPublicKey: string; + + it('should create a new r1 key and add as a new owner', async () => { + newKeyPair = genKey(); + newPublicKey = encodePublicKey(newKeyPair); + + expect(await account.r1IsOwner(newPublicKey)).to.be.false; + + const addOwnerTx = await account.r1AddOwner.populateTransaction( + newPublicKey, + ); + + const tx = await prepareTeeTx( + provider, + account, + addOwnerTx, + await validator.getAddress(), + keyPair, + ); + + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + await txReceipt.wait(); + + expect(await account.r1IsOwner(newPublicKey)).to.be.true; + + const expectedOwners = [newPublicKey, encodePublicKey(keyPair)]; + + expect(await account.r1ListOwners()).to.deep.eq(expectedOwners); + }); + + it('should remove an r1 key', async () => { + expect(await account.r1IsOwner(newPublicKey)).to.be.true; + + const removeOwnerTxData = + await account.r1RemoveOwner.populateTransaction(newPublicKey); + + const tx = await prepareTeeTx( + provider, + account, + removeOwnerTxData, + await validator.getAddress(), + keyPair, + ); + + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + await txReceipt.wait(); + + expect(await account.r1IsOwner(newPublicKey)).to.be.false; + + const expectedOwners = [encodePublicKey(keyPair)]; + + expect(await account.r1ListOwners()).to.deep.eq(expectedOwners); + }); + }); +}); diff --git a/new-test/utils/fixture.ts b/new-test/utils/fixture.ts index e276507..70c832d 100644 --- a/new-test/utils/fixture.ts +++ b/new-test/utils/fixture.ts @@ -22,6 +22,7 @@ export type fixtureTypes = [ export const fixture = async ( deployer: ClaveDeployer, + validatorOption: VALIDATORS = VALIDATORS.MOCK, ): Promise => { const keyPair = genKey(); @@ -29,15 +30,15 @@ export const fixture = async ( const registry = await deployer.registry(); const implementation = await deployer.implementation(batchCaller); const factory = await deployer.factory(implementation, registry); - const mockValidator = await deployer.validator(VALIDATORS.MOCK); - const account = await deployer.account(keyPair, factory, mockValidator); + const validator = await deployer.validator(validatorOption); + const account = await deployer.account(keyPair, factory, validator); return [ batchCaller, registry, implementation, factory, - mockValidator, + validator, account, keyPair, ]; diff --git a/new-test/utils/names.ts b/new-test/utils/names.ts index f7b32e4..ec2b5ff 100644 --- a/new-test/utils/names.ts +++ b/new-test/utils/names.ts @@ -15,7 +15,7 @@ export const CONTRACT_NAMES = { export enum VALIDATORS { MOCK = 'MockValidator', - TEE = 'TeeValidator', + TEE = 'TEEValidator', EOA = 'EOAValidator', PASSKEY = 'PasskeyValidator', } From 3294699fda6fa29a2168f5de34e239111fb5e812 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Thu, 12 Sep 2024 22:34:27 +0300 Subject: [PATCH 049/103] [clave-contracts] refactor: ownermanager tests --- .../accounts/managers/ownermanager.test.ts | 18 ++++-------------- new-test/utils/fixture.ts | 2 +- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/new-test/accounts/managers/ownermanager.test.ts b/new-test/accounts/managers/ownermanager.test.ts index d2c381e..67d808d 100644 --- a/new-test/accounts/managers/ownermanager.test.ts +++ b/new-test/accounts/managers/ownermanager.test.ts @@ -5,7 +5,6 @@ */ import { expect } from 'chai'; import type { ec } from 'elliptic'; -import { parseEther } from 'ethers'; import * as hre from 'hardhat'; import type { Contract, Wallet } from 'zksync-ethers'; import { Provider, utils } from 'zksync-ethers'; @@ -13,7 +12,6 @@ import { Provider, utils } from 'zksync-ethers'; import { LOCAL_RICH_WALLETS, getWallet } from '../../../deploy/utils'; import { ClaveDeployer } from '../../utils/deployer'; import { fixture } from '../../utils/fixture'; -import { VALIDATORS } from '../../utils/names'; import { encodePublicKey, genKey } from '../../utils/p256'; import { prepareTeeTx } from '../../utils/transactions'; @@ -21,12 +19,10 @@ describe('Clave Contracts - Manager tests', () => { let deployer: ClaveDeployer; let provider: Provider; let richWallet: Wallet; - let validator: Contract; + let mockValidator: Contract; let account: Contract; let keyPair: ec.KeyPair; - let erc20: Contract; - before(async () => { richWallet = getWallet(hre, LOCAL_RICH_WALLETS[0].privateKey); deployer = new ClaveDeployer(hre, richWallet); @@ -34,17 +30,11 @@ describe('Clave Contracts - Manager tests', () => { cacheTimeout: -1, }); - [, , , , validator, account, keyPair] = await fixture( - deployer, - // VALIDATORS.TEE, // FIXME: non-Mock validators not working - ); + [, , , , mockValidator, account, keyPair] = await fixture(deployer); const accountAddress = await account.getAddress(); await deployer.fund(10000, accountAddress); - - erc20 = await deployer.deployCustomContract('MockStable', []); - await erc20.mint(accountAddress, parseEther('100000')); }); describe('Owner Manager', () => { @@ -70,7 +60,7 @@ describe('Clave Contracts - Manager tests', () => { provider, account, addOwnerTx, - await validator.getAddress(), + await mockValidator.getAddress(), keyPair, ); @@ -96,7 +86,7 @@ describe('Clave Contracts - Manager tests', () => { provider, account, removeOwnerTxData, - await validator.getAddress(), + await mockValidator.getAddress(), keyPair, ); diff --git a/new-test/utils/fixture.ts b/new-test/utils/fixture.ts index 70c832d..9dc7414 100644 --- a/new-test/utils/fixture.ts +++ b/new-test/utils/fixture.ts @@ -15,7 +15,7 @@ export type fixtureTypes = [ registry: Contract, implementation: Contract, factory: Contract, - mockValidator: Contract, + validator: Contract, account: Contract, keyPair: ec.KeyPair, ]; From 3424fcb3368daa418f4ec1c045c266eb21949021 Mon Sep 17 00:00:00 2001 From: tahos81 Date: Fri, 13 Sep 2024 09:29:57 +0300 Subject: [PATCH 050/103] index appa --- subgraph/abis/ZtakeV2.json | 535 +++++++++++++++++++++++++++++++ subgraph/src/clave-appa-stake.ts | 124 +++++++ subgraph/subgraph.yaml | 27 ++ 3 files changed, 686 insertions(+) create mode 100644 subgraph/abis/ZtakeV2.json create mode 100644 subgraph/src/clave-appa-stake.ts diff --git a/subgraph/abis/ZtakeV2.json b/subgraph/abis/ZtakeV2.json new file mode 100644 index 0000000..2c51e19 --- /dev/null +++ b/subgraph/abis/ZtakeV2.json @@ -0,0 +1,535 @@ +[ + { + "inputs": [ + { + "internalType": "uint256", + "name": "_limitPerUser", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_totalLimit", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_stakeToken", + "type": "address" + }, + { + "internalType": "address", + "name": "_rewardToken", + "type": "address" + }, + { + "internalType": "address", + "name": "_registry", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reward", + "type": "uint256" + } + ], + "name": "RewardPaid", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Staked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Withdrawn", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "duration", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_account", + "type": "address" + } + ], + "name": "earned", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "finishAt", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_rate", + "type": "uint256" + } + ], + "name": "getApy", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getReward", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "lastTimeRewardApplicable", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "limitPerUser", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "notifyRewardAmount", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "registry", + "outputs": [ + { + "internalType": "contract IClaveRegistry", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "rewardPerToken", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "rewardPerTokenStored", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "rewardRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "rewardToken", + "outputs": [ + { + "internalType": "contract IERC20Metadata", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "rewards", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_limitPerUser", + "type": "uint256" + } + ], + "name": "setLimitPerUser", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_duration", + "type": "uint256" + } + ], + "name": "setRewardsDuration", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_totalLimit", + "type": "uint256" + } + ], + "name": "setTotalLimit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "stake", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "stakeToken", + "outputs": [ + { + "internalType": "contract IERC20Metadata", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalLimit", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_registry", + "type": "address" + } + ], + "name": "updateRegistry", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "updatedAt", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "userRewardPerTokenPaid", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "withdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "withdrawReward", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/subgraph/src/clave-appa-stake.ts b/subgraph/src/clave-appa-stake.ts new file mode 100644 index 0000000..c933772 --- /dev/null +++ b/subgraph/src/clave-appa-stake.ts @@ -0,0 +1,124 @@ +/** + * Copyright Clave - All Rights Reserved + * Unauthorized copying of this file, via any medium is strictly prohibited + * Proprietary and confidential + */ + +/* eslint-disable @typescript-eslint/consistent-type-imports */ +import { + RewardPaid as RewardPaidEvent, + Staked as StakedEvent, + Withdrawn as WithdrawnEvent, +} from '../generated/ClaveAPPAStake/ZtakeV2'; +import { ClaveAccount } from '../generated/schema'; +import { + getOrCreateDailyEarnFlow, + getOrCreateDay, + getOrCreateEarnPosition, + getOrCreateMonth, + getOrCreateMonthlyEarnFlow, + getOrCreateWeek, + getOrCreateWeeklyEarnFlow, +} from './helpers'; + +const protocol = 'Clave'; + +export function handleStaked(event: StakedEvent): void { + const account = ClaveAccount.load(event.params.user); + if (!account) { + return; + } + + const pool = event.address; + const token = event.params.token; + const amount = event.params.amount; + + const day = getOrCreateDay(event.block.timestamp); + const week = getOrCreateWeek(event.block.timestamp); + const month = getOrCreateMonth(event.block.timestamp); + + const dailyEarnFlow = getOrCreateDailyEarnFlow(day, token, protocol); + const weeklyEarnFlow = getOrCreateWeeklyEarnFlow(week, token, protocol); + const monthlyEarnFlow = getOrCreateMonthlyEarnFlow(month, token, protocol); + const earnPosition = getOrCreateEarnPosition( + account, + pool, + token, + protocol, + ); + + dailyEarnFlow.amountIn = dailyEarnFlow.amountIn.plus(amount); + weeklyEarnFlow.amountIn = weeklyEarnFlow.amountIn.plus(amount); + monthlyEarnFlow.amountIn = monthlyEarnFlow.amountIn.plus(amount); + earnPosition.invested = earnPosition.invested.plus(amount); + + dailyEarnFlow.save(); + weeklyEarnFlow.save(); + monthlyEarnFlow.save(); + earnPosition.save(); +} + +export function handleWithdrawn(event: WithdrawnEvent): void { + const account = ClaveAccount.load(event.transaction.from); + if (!account) { + return; + } + + const pool = event.address; + const token = event.params.token; + const amount = event.params.amount; + + const day = getOrCreateDay(event.block.timestamp); + const week = getOrCreateWeek(event.block.timestamp); + const month = getOrCreateMonth(event.block.timestamp); + + const dailyEarnFlow = getOrCreateDailyEarnFlow(day, token, protocol); + const weeklyEarnFlow = getOrCreateWeeklyEarnFlow(week, token, protocol); + const monthlyEarnFlow = getOrCreateMonthlyEarnFlow(month, token, protocol); + const earnPosition = getOrCreateEarnPosition( + account, + pool, + token, + protocol, + ); + + dailyEarnFlow.amountOut = dailyEarnFlow.amountOut.plus(amount); + weeklyEarnFlow.amountOut = weeklyEarnFlow.amountOut.plus(amount); + monthlyEarnFlow.amountOut = monthlyEarnFlow.amountOut.plus(amount); + earnPosition.invested = earnPosition.invested.minus(amount); + + dailyEarnFlow.save(); + weeklyEarnFlow.save(); + monthlyEarnFlow.save(); + earnPosition.save(); +} + +export function handleRewardPaid(event: RewardPaidEvent): void { + const account = ClaveAccount.load(event.params.user); + if (!account) { + return; + } + + const pool = event.address; + const token = event.params.token; + const amount = event.params.reward; + + const day = getOrCreateDay(event.block.timestamp); + const week = getOrCreateWeek(event.block.timestamp); + const month = getOrCreateMonth(event.block.timestamp); + + const dailyEarnFlow = getOrCreateDailyEarnFlow(day, token, protocol); + const weeklyEarnFlow = getOrCreateWeeklyEarnFlow(week, token, protocol); + const monthlyEarnFlow = getOrCreateMonthlyEarnFlow(month, token, protocol); + const earnPosition = getOrCreateEarnPosition( + account, + pool, + token, + protocol, + ); + + dailyEarnFlow.claimedGain = dailyEarnFlow.claimedGain.plus(amount); + weeklyEarnFlow.claimedGain = weeklyEarnFlow.claimedGain.plus(amount); + monthlyEarnFlow.claimedGain = monthlyEarnFlow.claimedGain.plus(amount); + earnPosition.normalGain = earnPosition.normalGain.plus(amount); +} diff --git a/subgraph/subgraph.yaml b/subgraph/subgraph.yaml index 1fe2351..753e355 100644 --- a/subgraph/subgraph.yaml +++ b/subgraph/subgraph.yaml @@ -399,6 +399,33 @@ dataSources: - event: RewardPaid(indexed address,uint256) handler: handleRewardPaid file: ./src/clave-zk-stake.ts + - kind: ethereum + name: ClaveAPPAStake + network: zksync-era + source: + address: '0x20999BD9fA71175e4A430CDB7950a66916E6F4d1' + abi: ZtakeV2 + startBlock: 41763539 + mapping: + kind: ethereum/events + apiVersion: 0.0.7 + language: wasm/assemblyscript + entities: + - DailyEarnFlow + - WeeklyEarnFlow + - MonthlyEarnFlow + - EarnPosition + abis: + - name: ZtakeV2 + file: ./abis/ZtakeV2.json + eventHandlers: + - event: Staked(indexed address,indexed address,uint256) + handler: handleStaked + - event: Withdrawn(indexed address,indexed address,uint256) + handler: handleWithdrawn + - event: RewardPaid(indexed address,indexed address,uint256) + handler: handleRewardPaid + file: ./src/clave-appa-stake.ts - kind: ethereum name: MeowStake network: zksync-era From 436137d56cb8a07a832648fdfd42a71c04a7aef4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Sat, 14 Sep 2024 00:23:26 +0300 Subject: [PATCH 051/103] [clave-contracts] refactor: use teevalidator --- new-test/accounts/managers/ownermanager.test.ts | 12 +++++++++++- new-test/utils/transactions.ts | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/new-test/accounts/managers/ownermanager.test.ts b/new-test/accounts/managers/ownermanager.test.ts index 67d808d..3feeda9 100644 --- a/new-test/accounts/managers/ownermanager.test.ts +++ b/new-test/accounts/managers/ownermanager.test.ts @@ -12,6 +12,7 @@ import { Provider, utils } from 'zksync-ethers'; import { LOCAL_RICH_WALLETS, getWallet } from '../../../deploy/utils'; import { ClaveDeployer } from '../../utils/deployer'; import { fixture } from '../../utils/fixture'; +import { VALIDATORS } from '../../utils/names'; import { encodePublicKey, genKey } from '../../utils/p256'; import { prepareTeeTx } from '../../utils/transactions'; @@ -30,7 +31,10 @@ describe('Clave Contracts - Manager tests', () => { cacheTimeout: -1, }); - [, , , , mockValidator, account, keyPair] = await fixture(deployer); + [, , , , mockValidator, account, keyPair] = await fixture( + deployer, + VALIDATORS.TEE, + ); const accountAddress = await account.getAddress(); @@ -76,6 +80,10 @@ describe('Clave Contracts - Manager tests', () => { expect(await account.r1ListOwners()).to.deep.eq(expectedOwners); }); + // it('should send a tx with the new key', async () => { + // const amount = parseEther('1'); + // }); + it('should remove an r1 key', async () => { expect(await account.r1IsOwner(newPublicKey)).to.be.true; @@ -101,5 +109,7 @@ describe('Clave Contracts - Manager tests', () => { expect(await account.r1ListOwners()).to.deep.eq(expectedOwners); }); + + // it('should not send any tx with the removed key', async () => {}); }); }); diff --git a/new-test/utils/transactions.ts b/new-test/utils/transactions.ts index 3290971..d13dddb 100644 --- a/new-test/utils/transactions.ts +++ b/new-test/utils/transactions.ts @@ -40,7 +40,7 @@ export async function prepareMockTx( ...tx, from: await account.getAddress(), nonce: await provider.getTransactionCount(await account.getAddress()), - gasLimit: 10_000_000, + gasLimit: 30_000_000, gasPrice: await provider.getGasPrice(), chainId: (await provider.getNetwork()).chainId, type: 113, From 55a61f0b0b6220c317cea65f566945db736444ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Sat, 14 Sep 2024 00:26:10 +0300 Subject: [PATCH 052/103] [clave-contracts] fix: signing for teevalidator tests --- new-test/utils/transactions.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/new-test/utils/transactions.ts b/new-test/utils/transactions.ts index d13dddb..743260b 100644 --- a/new-test/utils/transactions.ts +++ b/new-test/utils/transactions.ts @@ -5,7 +5,7 @@ */ import type { ec } from 'elliptic'; import type { BigNumberish } from 'ethers'; -import { ethers, parseEther } from 'ethers'; +import { ethers, parseEther, sha256 } from 'ethers'; import type { Contract, Provider, types } from 'zksync-ethers'; import { EIP712Signer, utils } from 'zksync-ethers'; @@ -140,7 +140,7 @@ export async function prepareTeeTx( const signedTxHash = EIP712Signer.getSignedDigest(tx); const abiCoder = ethers.AbiCoder.defaultAbiCoder(); - let signature = sign(signedTxHash.toString(), keyPair); + let signature = sign(sha256(signedTxHash.toString()), keyPair); signature = abiCoder.encode( ['bytes', 'address', 'bytes[]'], @@ -200,7 +200,7 @@ export async function prepareBatchTx( const signedTxHash = EIP712Signer.getSignedDigest(tx); - let signature = sign(signedTxHash.toString(), keyPair); + let signature = sign(sha256(signedTxHash.toString()), keyPair); signature = abiCoder.encode( ['bytes', 'address', 'bytes[]'], From 401992e404cfa39f7df050bbe36e3001bbc85284 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Sat, 14 Sep 2024 00:50:29 +0300 Subject: [PATCH 053/103] [clave-contracts] feat: improve owner manager tests with k1 keys --- .../accounts/managers/ownermanager.test.ts | 228 +++++++++++++----- test/clave.test.ts | 213 ---------------- 2 files changed, 164 insertions(+), 277 deletions(-) diff --git a/new-test/accounts/managers/ownermanager.test.ts b/new-test/accounts/managers/ownermanager.test.ts index 3feeda9..3edd71d 100644 --- a/new-test/accounts/managers/ownermanager.test.ts +++ b/new-test/accounts/managers/ownermanager.test.ts @@ -5,16 +5,17 @@ */ import { expect } from 'chai'; import type { ec } from 'elliptic'; +import { parseEther } from 'ethers'; import * as hre from 'hardhat'; -import type { Contract, Wallet } from 'zksync-ethers'; -import { Provider, utils } from 'zksync-ethers'; +import type { Contract } from 'zksync-ethers'; +import { Provider, Wallet, utils } from 'zksync-ethers'; import { LOCAL_RICH_WALLETS, getWallet } from '../../../deploy/utils'; import { ClaveDeployer } from '../../utils/deployer'; import { fixture } from '../../utils/fixture'; import { VALIDATORS } from '../../utils/names'; import { encodePublicKey, genKey } from '../../utils/p256'; -import { prepareTeeTx } from '../../utils/transactions'; +import { ethTransfer, prepareTeeTx } from '../../utils/transactions'; describe('Clave Contracts - Manager tests', () => { let deployer: ClaveDeployer; @@ -47,69 +48,168 @@ describe('Clave Contracts - Manager tests', () => { expect(await account.r1IsOwner(newPublicKey)).to.be.true; }); - let newKeyPair: ec.KeyPair; - let newPublicKey: string; - - it('should create a new r1 key and add as a new owner', async () => { - newKeyPair = genKey(); - newPublicKey = encodePublicKey(newKeyPair); - - expect(await account.r1IsOwner(newPublicKey)).to.be.false; - - const addOwnerTx = await account.r1AddOwner.populateTransaction( - newPublicKey, - ); - - const tx = await prepareTeeTx( - provider, - account, - addOwnerTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - await txReceipt.wait(); - - expect(await account.r1IsOwner(newPublicKey)).to.be.true; - - const expectedOwners = [newPublicKey, encodePublicKey(keyPair)]; - - expect(await account.r1ListOwners()).to.deep.eq(expectedOwners); + describe('Full tests with a new r1 key, adding-removing-validating', () => { + let newKeyPair: ec.KeyPair; + let newPublicKey: string; + + it('should create a new r1 key and add as a new owner', async () => { + newKeyPair = genKey(); + newPublicKey = encodePublicKey(newKeyPair); + + expect(await account.r1IsOwner(newPublicKey)).to.be.false; + + const addOwnerTx = await account.r1AddOwner.populateTransaction( + newPublicKey, + ); + + const tx = await prepareTeeTx( + provider, + account, + addOwnerTx, + await mockValidator.getAddress(), + keyPair, + ); + + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + await txReceipt.wait(); + + expect(await account.r1IsOwner(newPublicKey)).to.be.true; + + const expectedOwners = [newPublicKey, encodePublicKey(keyPair)]; + + expect(await account.r1ListOwners()).to.deep.eq(expectedOwners); + }); + + it('should send a tx with the new key', async () => { + const amount = parseEther('1'); + const richAddress = await richWallet.getAddress(); + const richBalanceBefore = await provider.getBalance( + richAddress, + ); + + const txData = ethTransfer(richAddress, amount); + const tx = await prepareTeeTx( + provider, + account, + txData, + await mockValidator.getAddress(), + newKeyPair, + ); + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + await txReceipt.wait(); + + const richBalanceAfter = await provider.getBalance(richAddress); + expect(richBalanceAfter).to.be.equal( + richBalanceBefore + amount, + ); + }); + + it('should remove an r1 key', async () => { + expect(await account.r1IsOwner(newPublicKey)).to.be.true; + + const removeOwnerTxData = + await account.r1RemoveOwner.populateTransaction( + newPublicKey, + ); + + const tx = await prepareTeeTx( + provider, + account, + removeOwnerTxData, + await mockValidator.getAddress(), + keyPair, + ); + + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + await txReceipt.wait(); + + expect(await account.r1IsOwner(newPublicKey)).to.be.false; + + const expectedOwners = [encodePublicKey(keyPair)]; + + expect(await account.r1ListOwners()).to.deep.eq(expectedOwners); + }); + + it('should not send any tx with the removed key', async () => { + const amount = parseEther('1'); + const richAddress = await richWallet.getAddress(); + const richBalanceBefore = await provider.getBalance( + richAddress, + ); + + expect(richBalanceBefore).to.be.greaterThan(amount); + + const txData = ethTransfer(richAddress, amount); + const tx = await prepareTeeTx( + provider, + account, + txData, + await mockValidator.getAddress(), + newKeyPair, + ); + await expect( + provider.broadcastTransaction(utils.serializeEip712(tx)), + ).to.be.reverted; + }); }); - // it('should send a tx with the new key', async () => { - // const amount = parseEther('1'); - // }); - - it('should remove an r1 key', async () => { - expect(await account.r1IsOwner(newPublicKey)).to.be.true; - - const removeOwnerTxData = - await account.r1RemoveOwner.populateTransaction(newPublicKey); - - const tx = await prepareTeeTx( - provider, - account, - removeOwnerTxData, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - await txReceipt.wait(); - - expect(await account.r1IsOwner(newPublicKey)).to.be.false; - - const expectedOwners = [encodePublicKey(keyPair)]; - - expect(await account.r1ListOwners()).to.deep.eq(expectedOwners); + describe('Non-full tests with a new k1 key, adding-removing, not validating', () => { + let newK1Address: string; + it('should add a new k1 key', async () => { + newK1Address = await Wallet.createRandom().getAddress(); + + expect(await account.k1IsOwner(newK1Address)).to.be.false; + + const addOwnerTx = await account.k1AddOwner.populateTransaction( + newK1Address, + ); + const tx = await prepareTeeTx( + provider, + account, + addOwnerTx, + await mockValidator.getAddress(), + keyPair, + ); + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + await txReceipt.wait(); + + expect(await account.k1IsOwner(newK1Address)).to.be.true; + + const expectedOwners = [newK1Address]; + expect(await account.k1ListOwners()).to.deep.eq(expectedOwners); + }); + + it('should remove the new k1 key', async () => { + expect(await account.k1IsOwner(newK1Address)).to.be.true; + + const removeOwnerTx = + await account.k1RemoveOwner.populateTransaction( + newK1Address, + ); + const tx = await prepareTeeTx( + provider, + account, + removeOwnerTx, + await mockValidator.getAddress(), + keyPair, + ); + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + await txReceipt.wait(); + + expect(await account.k1IsOwner(newK1Address)).to.be.false; + const expectedOwners: Array = []; + expect(await account.k1ListOwners()).to.deep.eq(expectedOwners); + }); }); - - // it('should not send any tx with the removed key', async () => {}); }); }); diff --git a/test/clave.test.ts b/test/clave.test.ts index 03500db..d934f10 100644 --- a/test/clave.test.ts +++ b/test/clave.test.ts @@ -137,220 +137,8 @@ beforeEach(async () => { }); describe('Account no module no hook TEE validator', function () { - describe('Should', function () { - it('Have correct state after deployment', async function () { - expect(await provider.getBalance(await account.getAddress())).to.eq( - parseEther('100'), - ); - - const expectedR1Validators = [await mockValidator.getAddress()]; - const expectedK1Validators: Array = []; - const expectedR1Owners = [encodePublicKey(keyPair)]; - const expectedK1Owners: Array = []; - const expectedModules: Array = []; - const expectedHooks: Array = []; - const expectedImplementation = await implementation.getAddress(); - - expect(await account.r1ListValidators()).to.deep.eq( - expectedR1Validators, - ); - expect(await account.k1ListValidators()).to.deep.eq( - expectedK1Validators, - ); - expect(await account.r1ListOwners()).to.deep.eq(expectedR1Owners); - expect(await account.k1ListOwners()).to.deep.eq(expectedK1Owners); - expect(await account.listModules()).to.deep.eq(expectedModules); - expect(await account.listHooks(false)).to.deep.eq(expectedHooks); - expect(await account.listHooks(true)).to.deep.eq(expectedHooks); - expect(await account.implementation()).to.eq( - expectedImplementation, - ); - }); - - it('Registers contract to the registry', async function () { - expect(await registry.isClave(await account.getAddress())).to.be - .true; - }); - - it('Not show other contracts in registry', async function () { - expect(await registry.isClave(await factory.getAddress())).not.to.be - .true; - }); - - it('Transfer ETH correctly', async function () { - const amount = parseEther('10'); - const delta = parseEther('0.01'); - - const accountBalanceBefore = await provider.getBalance( - await account.getAddress(), - ); - const receiverBalanceBefore = await provider.getBalance( - await richWallet.getAddress(), - ); - - const transfer = ethTransfer(await richWallet.getAddress(), amount); - const tx = await prepareTeeTx( - provider, - account, - transfer, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - - await txReceipt.wait(); - - const accountBalanceAfter = await provider.getBalance( - await account.getAddress(), - ); - const receiverBalanceAfter = await provider.getBalance( - await richWallet.getAddress(), - ); - - expect(accountBalanceAfter).to.be.closeTo( - accountBalanceBefore - amount, - delta, - ); - expect(receiverBalanceAfter).to.eq(receiverBalanceBefore + amount); - }); - - it('Make batch transaction correctly', async function () { - const accountBalanceBefore = await provider.getBalance( - await account.getAddress(), - ); - - const receiver1 = await Wallet.createRandom().getAddress(); - const receiver2 = await Wallet.createRandom().getAddress(); - - const calls: Array = [ - { - target: receiver1, - allowFailure: false, - value: parseEther('0.1'), - callData: '0x', - }, - { - target: receiver2, - allowFailure: false, - value: parseEther('0.2'), - callData: '0x', - }, - ]; - - const batchTx = await prepareBatchTx( - provider, - account, - await batchCaller.getAddress(), - calls, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(batchTx), - ); - await txReceipt.wait(); - - const accountBalanceAfter = await provider.getBalance( - await account.getAddress(), - ); - - expect(accountBalanceAfter).to.be.closeTo( - accountBalanceBefore - parseEther('0.3'), - parseEther('0.01'), - ); - expect(await provider.getBalance(receiver1)).to.eq( - parseEther('0.1'), - ); - expect(await provider.getBalance(receiver2)).to.eq( - parseEther('0.2'), - ); - }); - }); - describe('Owner manager', function () { describe('Should not revert when', function () { - it('Adds a new r1 owner correctly', async function () { - const newKeyPair = genKey(); - const newPublicKey = encodePublicKey(newKeyPair); - - expect(await account.r1IsOwner(newPublicKey)).to.be.false; - - const addOwnerTx = await account.r1AddOwner.populateTransaction( - newPublicKey, - ); - - const tx = await prepareTeeTx( - provider, - account, - addOwnerTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - await txReceipt.wait(); - - expect(await account.r1IsOwner(newPublicKey)).to.be.true; - - const expectedOwners = [newPublicKey, encodePublicKey(keyPair)]; - - expect(await account.r1ListOwners()).to.deep.eq(expectedOwners); - }); - - it('Removes an r1 owner correctly', async function () { - const newKeyPair = genKey(); - const newPublicKey = encodePublicKey(newKeyPair); - - const addOwnerTx = await account.r1AddOwner.populateTransaction( - newPublicKey, - ); - - const tx = await prepareTeeTx( - provider, - account, - addOwnerTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - await txReceipt.wait(); - - expect(await account.r1IsOwner(newPublicKey)).to.be.true; - - const removeOwnerTx = - await account.r1RemoveOwner.populateTransaction( - newPublicKey, - ); - - const tx2 = await prepareTeeTx( - provider, - account, - removeOwnerTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt2 = await provider.broadcastTransaction( - utils.serializeEip712(tx2), - ); - await txReceipt2.wait(); - - expect(await account.r1IsOwner(newPublicKey)).to.be.false; - - const expectedOwners = [encodePublicKey(keyPair)]; - - expect(await account.r1ListOwners()).to.deep.eq(expectedOwners); - }); - it('Adds a new k1 owner correctly', async function () { const newAddress = await Wallet.createRandom().getAddress(); @@ -367,7 +155,6 @@ describe('Account no module no hook TEE validator', function () { await mockValidator.getAddress(), keyPair, ); - const txReceipt = await provider.broadcastTransaction( utils.serializeEip712(tx), ); From f8004bd80b93eb5ad497679b4a9ff6a5deacc75d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Sat, 14 Sep 2024 18:14:29 +0300 Subject: [PATCH 054/103] [clave-contracts] feat: fully migrate ownermanager tests and separate fns as utils --- .../accounts/managers/ownermanager.test.ts | 279 +++++++++++++++--- new-test/utils/managers/ownermanager.ts | 129 ++++++++ 2 files changed, 359 insertions(+), 49 deletions(-) create mode 100644 new-test/utils/managers/ownermanager.ts diff --git a/new-test/accounts/managers/ownermanager.test.ts b/new-test/accounts/managers/ownermanager.test.ts index 3edd71d..6960c3b 100644 --- a/new-test/accounts/managers/ownermanager.test.ts +++ b/new-test/accounts/managers/ownermanager.test.ts @@ -3,9 +3,10 @@ * Unauthorized copying of this file, via any medium is strictly prohibited * Proprietary and confidential */ -import { expect } from 'chai'; +import { assert, expect } from 'chai'; import type { ec } from 'elliptic'; -import { parseEther } from 'ethers'; +import { ZeroAddress, parseEther } from 'ethers'; +import type { BytesLike } from 'ethers'; import * as hre from 'hardhat'; import type { Contract } from 'zksync-ethers'; import { Provider, Wallet, utils } from 'zksync-ethers'; @@ -13,6 +14,13 @@ import { Provider, Wallet, utils } from 'zksync-ethers'; import { LOCAL_RICH_WALLETS, getWallet } from '../../../deploy/utils'; import { ClaveDeployer } from '../../utils/deployer'; import { fixture } from '../../utils/fixture'; +import { + addK1Key, + addR1Key, + removeK1Key, + removeR1Key, + resetOwners, +} from '../../utils/managers/ownermanager'; import { VALIDATORS } from '../../utils/names'; import { encodePublicKey, genKey } from '../../utils/p256'; import { ethTransfer, prepareTeeTx } from '../../utils/transactions'; @@ -58,27 +66,17 @@ describe('Clave Contracts - Manager tests', () => { expect(await account.r1IsOwner(newPublicKey)).to.be.false; - const addOwnerTx = await account.r1AddOwner.populateTransaction( - newPublicKey, - ); - - const tx = await prepareTeeTx( + await addR1Key( provider, account, - addOwnerTx, - await mockValidator.getAddress(), + mockValidator, + newPublicKey, keyPair, ); - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - await txReceipt.wait(); - expect(await account.r1IsOwner(newPublicKey)).to.be.true; const expectedOwners = [newPublicKey, encodePublicKey(keyPair)]; - expect(await account.r1ListOwners()).to.deep.eq(expectedOwners); }); @@ -111,24 +109,14 @@ describe('Clave Contracts - Manager tests', () => { it('should remove an r1 key', async () => { expect(await account.r1IsOwner(newPublicKey)).to.be.true; - const removeOwnerTxData = - await account.r1RemoveOwner.populateTransaction( - newPublicKey, - ); - - const tx = await prepareTeeTx( + await removeR1Key( provider, account, - removeOwnerTxData, - await mockValidator.getAddress(), + mockValidator, + newPublicKey, keyPair, ); - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - await txReceipt.wait(); - expect(await account.r1IsOwner(newPublicKey)).to.be.false; const expectedOwners = [encodePublicKey(keyPair)]; @@ -166,20 +154,13 @@ describe('Clave Contracts - Manager tests', () => { expect(await account.k1IsOwner(newK1Address)).to.be.false; - const addOwnerTx = await account.k1AddOwner.populateTransaction( - newK1Address, - ); - const tx = await prepareTeeTx( + await addK1Key( provider, account, - addOwnerTx, - await mockValidator.getAddress(), + mockValidator, + newK1Address, keyPair, ); - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - await txReceipt.wait(); expect(await account.k1IsOwner(newK1Address)).to.be.true; @@ -190,26 +171,226 @@ describe('Clave Contracts - Manager tests', () => { it('should remove the new k1 key', async () => { expect(await account.k1IsOwner(newK1Address)).to.be.true; - const removeOwnerTx = - await account.k1RemoveOwner.populateTransaction( - newK1Address, - ); - const tx = await prepareTeeTx( + await removeK1Key( provider, account, - removeOwnerTx, - await mockValidator.getAddress(), + mockValidator, + newK1Address, keyPair, ); - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - await txReceipt.wait(); expect(await account.k1IsOwner(newK1Address)).to.be.false; const expectedOwners: Array = []; expect(await account.k1ListOwners()).to.deep.eq(expectedOwners); }); }); + + describe('Additinal tests for r1 and k1 keys', () => { + let replacedKeyPair: ec.KeyPair; + + it('Should reset owners', async () => { + const newKeyPair = genKey(); + const newPublicKey = encodePublicKey(newKeyPair); + await addR1Key( + provider, + account, + mockValidator, + newPublicKey, + keyPair, + ); + + const newK1Address = await Wallet.createRandom().getAddress(); + await addK1Key( + provider, + account, + mockValidator, + newK1Address, + keyPair, + ); + + const expectedR1Owners = [ + newPublicKey, + encodePublicKey(keyPair), + ]; + expect(await account.r1ListOwners()).to.deep.eq( + expectedR1Owners, + ); + + const expectedK1Owners = [newK1Address]; + expect(await account.k1ListOwners()).to.deep.eq( + expectedK1Owners, + ); + + await resetOwners( + provider, + account, + mockValidator, + newPublicKey, + keyPair, + ); + replacedKeyPair = newKeyPair; + + const expectedNewR1Owners = [encodePublicKey(replacedKeyPair)]; + const expectedNewK1Owners: Array = []; + + expect(await account.r1ListOwners()).to.deep.eq( + expectedNewR1Owners, + ); + expect(await account.k1ListOwners()).to.deep.eq( + expectedNewK1Owners, + ); + }); + + it('Should revert the r1 owner with invalid length', async () => { + let invalidLength = Math.ceil(Math.random() * 200) * 2; + invalidLength = invalidLength === 128 ? 130 : invalidLength; + + const invalidPubkey = '0x' + 'C'.repeat(invalidLength); + + try { + await addR1Key( + provider, + account, + mockValidator, + invalidPubkey, + replacedKeyPair, + ); + assert(false, 'Should revert'); + } catch (err) {} + }); + + it('Should revert adding new r1 or k1 owner with unauthorized msg.sender', async function () { + const newKeyPair = genKey(); + const newPublicKey = encodePublicKey(newKeyPair); + + await expect( + account.r1AddOwner(newPublicKey), + ).to.be.revertedWithCustomError( + account, + 'NOT_FROM_SELF_OR_MODULE', + ); + + const randomWallet = Wallet.createRandom().connect(provider); + + await expect( + account.k1AddOwner(await randomWallet.getAddress()), + ).to.be.revertedWithCustomError( + account, + 'NOT_FROM_SELF_OR_MODULE', + ); + }); + + it('Should revert removing r1 or k1 owners and resetting owners with unauthorized msg.sender, then should reset owners to the initial', async function () { + const newKeyPair = genKey(); + const newPublicKey = encodePublicKey(newKeyPair); + + await addR1Key( + provider, + account, + mockValidator, + newPublicKey, + replacedKeyPair, + ); + + expect(await account.r1IsOwner(newPublicKey)).to.be.true; + + await expect( + account.r1RemoveOwner(newPublicKey), + ).to.be.revertedWithCustomError( + account, + 'NOT_FROM_SELF_OR_MODULE', + ); + + const newAddress = await Wallet.createRandom().getAddress(); + + await addK1Key( + provider, + account, + mockValidator, + newAddress, + replacedKeyPair, + ); + + expect(await account.k1IsOwner(newAddress)).to.be.true; + + const randomWallet = Wallet.createRandom().connect(provider); + + await expect( + account.k1RemoveOwner(await randomWallet.getAddress()), + ).to.be.revertedWithCustomError( + account, + 'NOT_FROM_SELF_OR_MODULE', + ); + + await resetOwners( + provider, + account, + mockValidator, + encodePublicKey(replacedKeyPair), + replacedKeyPair, + ); + + const expectedNewR1Owners = [encodePublicKey(replacedKeyPair)]; + const expectedNewK1Owners: Array = []; + + expect(await account.r1ListOwners()).to.deep.eq( + expectedNewR1Owners, + ); + expect(await account.k1ListOwners()).to.deep.eq( + expectedNewK1Owners, + ); + + await expect( + account.resetOwners(newPublicKey), + ).to.be.revertedWithCustomError( + account, + 'NOT_FROM_SELF_OR_MODULE', + ); + }); + + it('Should revert adding zero address as k1 owner', async function () { + try { + await addK1Key( + provider, + account, + mockValidator, + ZeroAddress, + replacedKeyPair, + ); + assert(false, 'Should revert'); + } catch (err) {} + }); + + it('Should revert removing the last r1 owner', async function () { + try { + await removeR1Key( + provider, + account, + mockValidator, + encodePublicKey(replacedKeyPair), + replacedKeyPair, + ); + assert(false, 'Should revert'); + } catch (err) {} + }); + + it('Should revert resetting owners while new r1 owner has invalid length', async function () { + let invalidLength = Math.ceil(Math.random() * 200) * 2; + invalidLength = invalidLength === 128 ? 130 : invalidLength; + + const invalidPubkey = '0x' + 'C'.repeat(invalidLength); + + try { + await removeR1Key( + provider, + account, + mockValidator, + invalidPubkey, + replacedKeyPair, + ); + assert(false, 'Should revert'); + } catch (err) {} + }); + }); }); }); diff --git a/new-test/utils/managers/ownermanager.ts b/new-test/utils/managers/ownermanager.ts new file mode 100644 index 0000000..303d2e4 --- /dev/null +++ b/new-test/utils/managers/ownermanager.ts @@ -0,0 +1,129 @@ +/** + * Copyright Clave - All Rights Reserved + * Unauthorized copying of this file, via any medium is strictly prohibited + * Proprietary and confidential + */ +import type { ec } from 'elliptic'; +import type { Provider } from 'zksync-ethers'; +import { utils } from 'zksync-ethers'; +import type { Contract } from 'zksync-ethers'; + +import { prepareTeeTx } from '../transactions'; + +export async function addR1Key( + provider: Provider, + account: Contract, + validator: Contract, + newPublicKey: string, + keyPair: ec.KeyPair, +): Promise { + const addOwnerTx = await account.r1AddOwner.populateTransaction( + newPublicKey, + ); + const tx = await prepareTeeTx( + provider, + account, + addOwnerTx, + await validator.getAddress(), + keyPair, + ); + + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + await txReceipt.wait(); +} + +export async function addK1Key( + provider: Provider, + account: Contract, + validator: Contract, + newK1Address: string, + keyPair: ec.KeyPair, +): Promise { + const addOwnerTx = await account.k1AddOwner.populateTransaction( + newK1Address, + ); + const tx = await prepareTeeTx( + provider, + account, + addOwnerTx, + await validator.getAddress(), + keyPair, + ); + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + await txReceipt.wait(); +} + +export async function removeR1Key( + provider: Provider, + account: Contract, + validator: Contract, + removingPublicKey: string, + keyPair: ec.KeyPair, +): Promise { + const removeOwnerTxData = await account.r1RemoveOwner.populateTransaction( + removingPublicKey, + ); + const tx = await prepareTeeTx( + provider, + account, + removeOwnerTxData, + await validator.getAddress(), + keyPair, + ); + + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + await txReceipt.wait(); +} + +export async function removeK1Key( + provider: Provider, + account: Contract, + validator: Contract, + removingAddress: string, + keyPair: ec.KeyPair, +): Promise { + const removeOwnerTx = await account.k1RemoveOwner.populateTransaction( + removingAddress, + ); + const tx = await prepareTeeTx( + provider, + account, + removeOwnerTx, + await validator.getAddress(), + keyPair, + ); + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + await txReceipt.wait(); +} + +export async function resetOwners( + provider: Provider, + account: Contract, + validator: Contract, + newPublicKey: string, + keyPair: ec.KeyPair, +): Promise { + const resetOwnersTx = await account.resetOwners.populateTransaction( + newPublicKey, + ); + const tx = await prepareTeeTx( + provider, + account, + resetOwnersTx, + await validator.getAddress(), + keyPair, + ); + + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + await txReceipt.wait(); +} From 655dfb044569a3a1b6c2f7ed325b6406ef5e93a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Sat, 14 Sep 2024 18:32:14 +0300 Subject: [PATCH 055/103] [clave-contracts] chore: correct var naming --- .../accounts/managers/ownermanager.test.ts | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/new-test/accounts/managers/ownermanager.test.ts b/new-test/accounts/managers/ownermanager.test.ts index 6960c3b..19c09fb 100644 --- a/new-test/accounts/managers/ownermanager.test.ts +++ b/new-test/accounts/managers/ownermanager.test.ts @@ -29,7 +29,7 @@ describe('Clave Contracts - Manager tests', () => { let deployer: ClaveDeployer; let provider: Provider; let richWallet: Wallet; - let mockValidator: Contract; + let teeValidator: Contract; let account: Contract; let keyPair: ec.KeyPair; @@ -40,7 +40,7 @@ describe('Clave Contracts - Manager tests', () => { cacheTimeout: -1, }); - [, , , , mockValidator, account, keyPair] = await fixture( + [, , , , teeValidator, account, keyPair] = await fixture( deployer, VALIDATORS.TEE, ); @@ -69,7 +69,7 @@ describe('Clave Contracts - Manager tests', () => { await addR1Key( provider, account, - mockValidator, + teeValidator, newPublicKey, keyPair, ); @@ -92,7 +92,7 @@ describe('Clave Contracts - Manager tests', () => { provider, account, txData, - await mockValidator.getAddress(), + await teeValidator.getAddress(), newKeyPair, ); const txReceipt = await provider.broadcastTransaction( @@ -112,7 +112,7 @@ describe('Clave Contracts - Manager tests', () => { await removeR1Key( provider, account, - mockValidator, + teeValidator, newPublicKey, keyPair, ); @@ -138,7 +138,7 @@ describe('Clave Contracts - Manager tests', () => { provider, account, txData, - await mockValidator.getAddress(), + await teeValidator.getAddress(), newKeyPair, ); await expect( @@ -157,7 +157,7 @@ describe('Clave Contracts - Manager tests', () => { await addK1Key( provider, account, - mockValidator, + teeValidator, newK1Address, keyPair, ); @@ -174,7 +174,7 @@ describe('Clave Contracts - Manager tests', () => { await removeK1Key( provider, account, - mockValidator, + teeValidator, newK1Address, keyPair, ); @@ -194,7 +194,7 @@ describe('Clave Contracts - Manager tests', () => { await addR1Key( provider, account, - mockValidator, + teeValidator, newPublicKey, keyPair, ); @@ -203,7 +203,7 @@ describe('Clave Contracts - Manager tests', () => { await addK1Key( provider, account, - mockValidator, + teeValidator, newK1Address, keyPair, ); @@ -224,7 +224,7 @@ describe('Clave Contracts - Manager tests', () => { await resetOwners( provider, account, - mockValidator, + teeValidator, newPublicKey, keyPair, ); @@ -251,7 +251,7 @@ describe('Clave Contracts - Manager tests', () => { await addR1Key( provider, account, - mockValidator, + teeValidator, invalidPubkey, replacedKeyPair, ); @@ -287,7 +287,7 @@ describe('Clave Contracts - Manager tests', () => { await addR1Key( provider, account, - mockValidator, + teeValidator, newPublicKey, replacedKeyPair, ); @@ -306,7 +306,7 @@ describe('Clave Contracts - Manager tests', () => { await addK1Key( provider, account, - mockValidator, + teeValidator, newAddress, replacedKeyPair, ); @@ -325,7 +325,7 @@ describe('Clave Contracts - Manager tests', () => { await resetOwners( provider, account, - mockValidator, + teeValidator, encodePublicKey(replacedKeyPair), replacedKeyPair, ); @@ -353,7 +353,7 @@ describe('Clave Contracts - Manager tests', () => { await addK1Key( provider, account, - mockValidator, + teeValidator, ZeroAddress, replacedKeyPair, ); @@ -366,7 +366,7 @@ describe('Clave Contracts - Manager tests', () => { await removeR1Key( provider, account, - mockValidator, + teeValidator, encodePublicKey(replacedKeyPair), replacedKeyPair, ); @@ -384,7 +384,7 @@ describe('Clave Contracts - Manager tests', () => { await removeR1Key( provider, account, - mockValidator, + teeValidator, invalidPubkey, replacedKeyPair, ); From ad2efeadec4e027455a7570d1be1705b9aad1bab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Sat, 14 Sep 2024 20:43:52 +0300 Subject: [PATCH 056/103] [clave-contracts] feat: add validator manager tests and fns --- .../managers/validatormanager.test.ts | 351 ++++++++++++++++++ new-test/utils/managers/validatormanager.ts | 105 ++++++ 2 files changed, 456 insertions(+) create mode 100644 new-test/accounts/managers/validatormanager.test.ts create mode 100644 new-test/utils/managers/validatormanager.ts diff --git a/new-test/accounts/managers/validatormanager.test.ts b/new-test/accounts/managers/validatormanager.test.ts new file mode 100644 index 0000000..6438b1d --- /dev/null +++ b/new-test/accounts/managers/validatormanager.test.ts @@ -0,0 +1,351 @@ +/** + * Copyright Clave - All Rights Reserved + * Unauthorized copying of this file, via any medium is strictly prohibited + * Proprietary and confidential + */ +import { assert, expect } from 'chai'; +import type { ec } from 'elliptic'; +import { parseEther } from 'ethers'; +import * as hre from 'hardhat'; +import { Contract } from 'zksync-ethers'; +import { Provider, Wallet, utils } from 'zksync-ethers'; + +import { LOCAL_RICH_WALLETS, getWallet } from '../../../deploy/utils'; +import { ClaveDeployer } from '../../utils/deployer'; +import { fixture } from '../../utils/fixture'; +import { + addK1Validator, + addR1Validator, + removeK1Validator, + removeR1Validator, +} from '../../utils/managers/validatormanager'; +import { VALIDATORS } from '../../utils/names'; +import { ethTransfer, prepareMockTx } from '../../utils/transactions'; + +describe('Clave Contracts - Manager tests', () => { + let deployer: ClaveDeployer; + let provider: Provider; + let richWallet: Wallet; + let teeValidator: Contract; + let account: Contract; + let keyPair: ec.KeyPair; + + before(async () => { + richWallet = getWallet(hre, LOCAL_RICH_WALLETS[0].privateKey); + deployer = new ClaveDeployer(hre, richWallet); + provider = new Provider(hre.network.config.url, undefined, { + cacheTimeout: -1, + }); + + [, , , , teeValidator, account, keyPair] = await fixture( + deployer, + VALIDATORS.TEE, + ); + + const accountAddress = await account.getAddress(); + + await deployer.fund(10000, accountAddress); + }); + + describe('Validator Manager', () => { + it('should check existing validator', async () => { + const validatorAddress = await teeValidator.getAddress(); + + expect(await account.r1IsValidator(validatorAddress)).to.be.true; + }); + + describe('Full tests with r1 validator type, adding-removing-validating', () => { + let newR1Validator: Contract; + + it('should add a new r1 validator', async () => { + newR1Validator = await deployer.validator(VALIDATORS.MOCK); + const validatorAddress = await newR1Validator.getAddress(); + + expect(await account.r1IsValidator(validatorAddress)).to.be + .false; + + await addR1Validator( + provider, + account, + teeValidator, + newR1Validator, + keyPair, + ); + + expect(await account.r1IsValidator(validatorAddress)).to.be + .true; + + const expectedValidators = [ + validatorAddress, + await teeValidator.getAddress(), + ]; + + expect(await account.r1ListValidators()).to.deep.eq( + expectedValidators, + ); + }); + + it('should send a tx with the new r1 validator', async () => { + const amount = parseEther('1'); + const richAddress = await richWallet.getAddress(); + const richBalanceBefore = await provider.getBalance( + richAddress, + ); + + const txData = ethTransfer(richAddress, amount); + const tx = await prepareMockTx( + provider, + account, + txData, + await newR1Validator.getAddress(), + ); + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + await txReceipt.wait(); + + const richBalanceAfter = await provider.getBalance(richAddress); + expect(richBalanceAfter).to.be.equal( + richBalanceBefore + amount, + ); + }); + + it('should remove the new r1 validator', async () => { + const validatorAddress = await newR1Validator.getAddress(); + expect(await account.r1IsValidator(validatorAddress)).to.be + .true; + + await removeR1Validator( + provider, + account, + teeValidator, + newR1Validator, + keyPair, + ); + + expect(await account.r1IsValidator(validatorAddress)).to.be + .false; + + const expectedValidators = [await teeValidator.getAddress()]; + + expect(await account.r1ListValidators()).to.deep.eq( + expectedValidators, + ); + }); + }); + + describe('Non-full tests with k1 validator type, adding-removing, not-validating', () => { + let newK1Validator: Contract; + + it('should add a new k1 validator', async () => { + newK1Validator = await deployer.validator(VALIDATORS.EOA); + const validatorAddress = await newK1Validator.getAddress(); + + expect(await account.k1IsValidator(newK1Validator)).to.be.false; + + await addK1Validator( + provider, + account, + teeValidator, + newK1Validator, + keyPair, + ); + + expect(await account.k1IsValidator(newK1Validator)).to.be.true; + + const expectedValidators = [validatorAddress]; + + expect(await account.k1ListValidators()).to.deep.eq( + expectedValidators, + ); + }); + + it('should remove a k1 validator', async () => { + const validatorAddress = await newK1Validator.getAddress(); + expect(await account.k1IsValidator(validatorAddress)).to.be + .true; + + await removeK1Validator( + provider, + account, + teeValidator, + newK1Validator, + keyPair, + ); + + expect(await account.r1IsValidator(validatorAddress)).to.be + .false; + + expect(await account.k1ListValidators()).to.deep.eq([]); + }); + }); + + describe('Additional tests for r1 and k1 validators', () => { + it('should revert adding new r1 and k1 validator with unauthorized msg.sender', async () => { + const newR1Validator = await deployer.validator( + VALIDATORS.MOCK, + ); + const r1ValidatorAddress = await newR1Validator.getAddress(); + + const newK1Validator = await deployer.validator(VALIDATORS.EOA); + const k1ValidatorAddress = await newK1Validator.getAddress(); + + await expect( + account.r1AddValidator(r1ValidatorAddress), + ).to.be.revertedWithCustomError( + account, + 'NOT_FROM_SELF_OR_MODULE', + ); + + await expect( + account.k1AddValidator(k1ValidatorAddress), + ).to.be.revertedWithCustomError( + account, + 'NOT_FROM_SELF_OR_MODULE', + ); + }); + + it('should revert adding new r1 and k1 validator with unauthorized msg.sender', async () => { + const newR1Validator = await deployer.validator( + VALIDATORS.MOCK, + ); + const r1ValidatorAddress = await newR1Validator.getAddress(); + + await addR1Validator( + provider, + account, + teeValidator, + newR1Validator, + keyPair, + ); + expect(await account.r1IsValidator(r1ValidatorAddress)).to.be + .true; + + await expect( + account.r1RemoveValidator( + await newR1Validator.getAddress(), + ), + ).to.be.revertedWithCustomError( + account, + 'NOT_FROM_SELF_OR_MODULE', + ); + + const newK1Validator = await deployer.validator(VALIDATORS.EOA); + const k1ValidatorAddress = await newK1Validator.getAddress(); + + await addK1Validator( + provider, + account, + teeValidator, + newK1Validator, + keyPair, + ); + expect(await account.k1IsValidator(k1ValidatorAddress)).to.be + .true; + + await expect( + account.k1RemoveValidator( + await newK1Validator.getAddress(), + ), + ).to.be.revertedWithCustomError( + account, + 'NOT_FROM_SELF_OR_MODULE', + ); + + await removeR1Validator( + provider, + account, + teeValidator, + newR1Validator, + keyPair, + ); + await removeK1Validator( + provider, + account, + teeValidator, + newK1Validator, + keyPair, + ); + }); + + it('should revert adding new r1 and k1 validator with WRONG interface', async () => { + const wrongR1Validator = await deployer.validator( + VALIDATORS.EOA, + ); + + const wrongK1Validator = await deployer.validator( + VALIDATORS.MOCK, + ); + + try { + await addR1Validator( + provider, + account, + teeValidator, + wrongR1Validator, + keyPair, + ); + assert(false, 'Should revert'); + } catch (err) {} + + try { + await addK1Validator( + provider, + account, + teeValidator, + wrongK1Validator, + keyPair, + ); + assert(false, 'Should revert'); + } catch (err) {} + }); + + it('should revert adding new r1 and k1 validator with NO interface', async () => { + const noInterfaceValidator = Wallet.createRandom(); + const validatorAddress = + await noInterfaceValidator.getAddress(); + + try { + await addR1Validator( + provider, + account, + teeValidator, + new Contract(validatorAddress, []), + keyPair, + ); + assert(false, 'Should revert'); + } catch (err) {} + + try { + await addK1Validator( + provider, + account, + teeValidator, + new Contract(validatorAddress, []), + keyPair, + ); + assert(false, 'Should revert'); + } catch (err) {} + }); + + it('should revert removing the last r1 validator', async () => { + const expectedValidators = [await teeValidator.getAddress()]; + + expect(await account.r1ListValidators()).to.deep.eq( + expectedValidators, + ); + + try { + await removeR1Validator( + provider, + account, + teeValidator, + teeValidator, + keyPair, + ); + assert(false, 'Should revert'); + } catch (err) {} + }); + }); + }); +}); diff --git a/new-test/utils/managers/validatormanager.ts b/new-test/utils/managers/validatormanager.ts new file mode 100644 index 0000000..c143c0e --- /dev/null +++ b/new-test/utils/managers/validatormanager.ts @@ -0,0 +1,105 @@ +/** + * Copyright Clave - All Rights Reserved + * Unauthorized copying of this file, via any medium is strictly prohibited + * Proprietary and confidential + */ +import type { ec } from 'elliptic'; +import type { Provider } from 'zksync-ethers'; +import type { Contract } from 'zksync-ethers'; +import { utils } from 'zksync-ethers'; + +import { prepareTeeTx } from '../transactions'; + +export async function addR1Validator( + provider: Provider, + account: Contract, + validator: Contract, + newR1Validator: Contract, + keyPair: ec.KeyPair, +): Promise { + const addValidatorTx = await account.r1AddValidator.populateTransaction( + await newR1Validator.getAddress(), + ); + const tx = await prepareTeeTx( + provider, + account, + addValidatorTx, + await validator.getAddress(), + keyPair, + ); + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + await txReceipt.wait(); +} + +export async function removeR1Validator( + provider: Provider, + account: Contract, + validator: Contract, + removingR1Validator: Contract, + keyPair: ec.KeyPair, +): Promise { + const removeValidatorTx = + await account.r1RemoveValidator.populateTransaction( + removingR1Validator, + ); + const tx = await prepareTeeTx( + provider, + account, + removeValidatorTx, + await validator.getAddress(), + keyPair, + ); + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + await txReceipt.wait(); +} + +export async function addK1Validator( + provider: Provider, + account: Contract, + validator: Contract, + newK1Validator: Contract, + keyPair: ec.KeyPair, +): Promise { + const addValidatorTx = await account.k1AddValidator.populateTransaction( + await newK1Validator.getAddress(), + ); + const tx = await prepareTeeTx( + provider, + account, + addValidatorTx, + await validator.getAddress(), + keyPair, + ); + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + await txReceipt.wait(); +} + +export async function removeK1Validator( + provider: Provider, + account: Contract, + validator: Contract, + removingK1Validator: Contract, + keyPair: ec.KeyPair, +): Promise { + const removeValidatorTx = + await account.k1RemoveValidator.populateTransaction( + removingK1Validator, + ); + const tx = await prepareTeeTx( + provider, + account, + removeValidatorTx, + await validator.getAddress(), + keyPair, + ); + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + await txReceipt.wait(); +} From c7a8bf8ed2a6c0c768b78b875b103cf3d72a6eec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Sat, 14 Sep 2024 22:02:23 +0300 Subject: [PATCH 057/103] [clave-contracts] feat: add module manager tests and fns --- .../accounts/managers/modulemanager.test.ts | 229 ++++++++++++++++++ new-test/utils/managers/modulemanager.ts | 62 +++++ 2 files changed, 291 insertions(+) create mode 100644 new-test/accounts/managers/modulemanager.test.ts create mode 100644 new-test/utils/managers/modulemanager.ts diff --git a/new-test/accounts/managers/modulemanager.test.ts b/new-test/accounts/managers/modulemanager.test.ts new file mode 100644 index 0000000..ec0da62 --- /dev/null +++ b/new-test/accounts/managers/modulemanager.test.ts @@ -0,0 +1,229 @@ +/** + * Copyright Clave - All Rights Reserved + * Unauthorized copying of this file, via any medium is strictly prohibited + * Proprietary and confidential + */ +import { assert, expect } from 'chai'; +import type { ec } from 'elliptic'; +import { AbiCoder, concat, parseEther } from 'ethers'; +import * as hre from 'hardhat'; +import { Contract, Provider, Wallet, utils } from 'zksync-ethers'; + +import { LOCAL_RICH_WALLETS, getWallet } from '../../../deploy/utils'; +import { ClaveDeployer } from '../../utils/deployer'; +import { fixture } from '../../utils/fixture'; +import { addModule, removeModule } from '../../utils/managers/modulemanager'; +import { VALIDATORS } from '../../utils/names'; +import { prepareTeeTx } from '../../utils/transactions'; + +describe('Clave Contracts - Manager tests', () => { + let deployer: ClaveDeployer; + let provider: Provider; + let richWallet: Wallet; + let teeValidator: Contract; + let account: Contract; + let keyPair: ec.KeyPair; + + before(async () => { + richWallet = getWallet(hre, LOCAL_RICH_WALLETS[0].privateKey); + deployer = new ClaveDeployer(hre, richWallet); + provider = new Provider(hre.network.config.url, undefined, { + cacheTimeout: -1, + }); + + [, , , , teeValidator, account, keyPair] = await fixture( + deployer, + VALIDATORS.TEE, + ); + + const accountAddress = await account.getAddress(); + + await deployer.fund(10000, accountAddress); + }); + + describe('Module Manager', () => { + let mockModule: Contract; + + describe('Main module functionalities', () => { + it('should check existing modules', async () => { + expect(await account.listModules()).to.deep.eq([]); + }); + + it('should add a new module', async () => { + mockModule = await deployer.deployCustomContract( + 'MockModule', + [], + ); + expect(await account.isModule(await mockModule.getAddress())).to + .be.false; + + const initData = AbiCoder.defaultAbiCoder().encode( + ['uint256'], + [parseEther('42')], + ); + await addModule( + provider, + account, + teeValidator, + mockModule, + initData, + keyPair, + ); + expect(await account.isModule(await mockModule.getAddress())).to + .be.true; + + const expectedModules = [await mockModule.getAddress()]; + expect(await account.listModules()).to.deep.eq(expectedModules); + }); + + it('should execute from a module', async () => { + const amount = parseEther('42'); + const delta = parseEther('0.01'); + + const accountBalanceBefore = await provider.getBalance( + await account.getAddress(), + ); + const receiverBalanceBefore = await provider.getBalance( + await richWallet.getAddress(), + ); + + await mockModule.testExecuteFromModule( + await account.getAddress(), + await richWallet.getAddress(), + ); + + const accountBalanceAfter = await provider.getBalance( + await account.getAddress(), + ); + const receiverBalanceAfter = await provider.getBalance( + await richWallet.getAddress(), + ); + + expect(accountBalanceAfter).to.be.closeTo( + accountBalanceBefore - amount, + delta, + ); + expect(receiverBalanceAfter).to.be.closeTo( + receiverBalanceBefore + amount, + delta, + ); + }); + + it('should remove a module', async () => { + expect(await account.isModule(await mockModule.getAddress())).to + .be.true; + + await removeModule( + provider, + account, + teeValidator, + mockModule, + keyPair, + ); + + expect(await account.isModule(await mockModule.getAddress())).to + .be.false; + const expectedModules: Array = []; + expect(await account.listModules()).to.deep.eq(expectedModules); + }); + }); + + describe('Alternative module cases', () => { + let newMockModule: Contract; + + before(async () => { + newMockModule = await deployer.deployCustomContract( + 'MockModule', + [], + ); + }); + + it('should revert adding module with unauthorized msg.sender', async () => { + const initData = AbiCoder.defaultAbiCoder().encode( + ['uint256'], + [parseEther('42')], + ); + const moduleAndData = concat([ + await newMockModule.getAddress(), + initData, + ]); + + await expect( + account.addModule(moduleAndData), + ).to.be.revertedWithCustomError( + account, + 'NOT_FROM_SELF_OR_MODULE', + ); + }); + + it('should revert removing module with unauthorized msg.sender', async () => { + const initData = AbiCoder.defaultAbiCoder().encode( + ['uint256'], + [parseEther('42')], + ); + await addModule( + provider, + account, + teeValidator, + newMockModule, + initData, + keyPair, + ); + expect(await account.isModule(await newMockModule.getAddress())) + .to.be.true; + + await expect( + account.removeModule(await newMockModule.getAddress()), + ).to.be.revertedWithCustomError( + account, + 'NOT_FROM_SELF_OR_MODULE', + ); + }); + + it('should revert adding module invalid moduleAndData length', async () => { + const moduleAndData = (await mockModule.getAddress()).slice( + 0, + 10, + ); + const addModuleTx = await account.addModule.populateTransaction( + moduleAndData, + ); + const tx = await prepareTeeTx( + provider, + account, + addModuleTx, + await teeValidator.getAddress(), + keyPair, + ); + + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + try { + await txReceipt.wait(); + assert(false, 'Should revert'); + } catch (err) {} + }); + + it('should revert adding module with no interface', async () => { + const noInterfaceModule = Wallet.createRandom(); + const initData = AbiCoder.defaultAbiCoder().encode( + ['uint256'], + [parseEther('42')], + ); + + try { + await addModule( + provider, + account, + teeValidator, + new Contract(await noInterfaceModule.getAddress(), []), + initData, + keyPair, + ); + assert(false, 'Should revert'); + } catch (err) {} + }); + }); + }); +}); diff --git a/new-test/utils/managers/modulemanager.ts b/new-test/utils/managers/modulemanager.ts new file mode 100644 index 0000000..17e355f --- /dev/null +++ b/new-test/utils/managers/modulemanager.ts @@ -0,0 +1,62 @@ +/** + * Copyright Clave - All Rights Reserved + * Unauthorized copying of this file, via any medium is strictly prohibited + * Proprietary and confidential + */ +import type { ec } from 'elliptic'; +import { concat } from 'ethers'; +import type { Contract, Provider } from 'zksync-ethers'; +import { utils } from 'zksync-ethers'; + +import { prepareTeeTx } from '../transactions'; + +export async function addModule( + provider: Provider, + account: Contract, + validator: Contract, + module: Contract, + initData: string, + keyPair: ec.KeyPair, +): Promise { + const moduleAndData = concat([await module.getAddress(), initData]); + + const addModuleTx = await account.addModule.populateTransaction( + moduleAndData, + ); + const tx = await prepareTeeTx( + provider, + account, + addModuleTx, + await validator.getAddress(), + keyPair, + ); + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + await txReceipt.wait(); +} + +export async function removeModule( + provider: Provider, + account: Contract, + validator: Contract, + module: Contract, + keyPair: ec.KeyPair, +): Promise { + const removeModuleTx = await account.removeModule.populateTransaction( + await module.getAddress(), + ); + + const tx = await prepareTeeTx( + provider, + account, + removeModuleTx, + await validator.getAddress(), + keyPair, + ); + + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + await txReceipt.wait(); +} From 0a35dab6d060374ce733a13df20cbd920a4d1978 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Sat, 14 Sep 2024 22:05:59 +0300 Subject: [PATCH 058/103] [clave-contracts] chore: organize the imports --- new-test/accounts/managers/ownermanager.test.ts | 2 +- new-test/accounts/managers/validatormanager.test.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/new-test/accounts/managers/ownermanager.test.ts b/new-test/accounts/managers/ownermanager.test.ts index 19c09fb..2e070c1 100644 --- a/new-test/accounts/managers/ownermanager.test.ts +++ b/new-test/accounts/managers/ownermanager.test.ts @@ -5,8 +5,8 @@ */ import { assert, expect } from 'chai'; import type { ec } from 'elliptic'; -import { ZeroAddress, parseEther } from 'ethers'; import type { BytesLike } from 'ethers'; +import { ZeroAddress, parseEther } from 'ethers'; import * as hre from 'hardhat'; import type { Contract } from 'zksync-ethers'; import { Provider, Wallet, utils } from 'zksync-ethers'; diff --git a/new-test/accounts/managers/validatormanager.test.ts b/new-test/accounts/managers/validatormanager.test.ts index 6438b1d..60be8d7 100644 --- a/new-test/accounts/managers/validatormanager.test.ts +++ b/new-test/accounts/managers/validatormanager.test.ts @@ -7,8 +7,7 @@ import { assert, expect } from 'chai'; import type { ec } from 'elliptic'; import { parseEther } from 'ethers'; import * as hre from 'hardhat'; -import { Contract } from 'zksync-ethers'; -import { Provider, Wallet, utils } from 'zksync-ethers'; +import { Contract, Provider, Wallet, utils } from 'zksync-ethers'; import { LOCAL_RICH_WALLETS, getWallet } from '../../../deploy/utils'; import { ClaveDeployer } from '../../utils/deployer'; From 93964590187954a6d04fb2830fbb49346e753575 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Sat, 14 Sep 2024 22:43:43 +0300 Subject: [PATCH 059/103] [clave-contracts] feat: add upgrade manager tests and fns --- .../accounts/managers/upgrademanager.test.ts | 94 +++++++++++++++++++ new-test/utils/managers/upgrademanager.ts | 33 +++++++ 2 files changed, 127 insertions(+) create mode 100644 new-test/accounts/managers/upgrademanager.test.ts create mode 100644 new-test/utils/managers/upgrademanager.ts diff --git a/new-test/accounts/managers/upgrademanager.test.ts b/new-test/accounts/managers/upgrademanager.test.ts new file mode 100644 index 0000000..4827c1d --- /dev/null +++ b/new-test/accounts/managers/upgrademanager.test.ts @@ -0,0 +1,94 @@ +/** + * Copyright Clave - All Rights Reserved + * Unauthorized copying of this file, via any medium is strictly prohibited + * Proprietary and confidential + */ +import { assert, expect } from 'chai'; +import type { ec } from 'elliptic'; +import * as hre from 'hardhat'; +import type { Contract, Wallet } from 'zksync-ethers'; +import { Provider } from 'zksync-ethers'; + +import { LOCAL_RICH_WALLETS, getWallet } from '../../../deploy/utils'; +import { ClaveDeployer } from '../../utils/deployer'; +import { fixture } from '../../utils/fixture'; +import { upgradeTx } from '../../utils/managers/upgrademanager'; +import { VALIDATORS } from '../../utils/names'; + +describe('Clave Contracts - Manager tests', () => { + let deployer: ClaveDeployer; + let provider: Provider; + let richWallet: Wallet; + let teeValidator: Contract; + let account: Contract; + let keyPair: ec.KeyPair; + + before(async () => { + richWallet = getWallet(hre, LOCAL_RICH_WALLETS[0].privateKey); + deployer = new ClaveDeployer(hre, richWallet); + provider = new Provider(hre.network.config.url, undefined, { + cacheTimeout: -1, + }); + + [, , , , teeValidator, account, keyPair] = await fixture( + deployer, + VALIDATORS.TEE, + ); + + const accountAddress = await account.getAddress(); + + await deployer.fund(10000, accountAddress); + }); + + describe('Upgrade Manager', () => { + let mockImplementation: Contract; + + before(async () => { + mockImplementation = await deployer.deployCustomContract( + 'MockImplementation', + [], + ); + }); + + it('should revert to a new implementation with unauthorized msg.sender', async () => { + await expect( + account.upgradeTo(await mockImplementation.getAddress()), + ).to.be.revertedWithCustomError(account, 'NOT_FROM_SELF'); + }); + + it('should upgrade to a new implementation', async () => { + expect(await account.implementation()).not.to.be.eq( + await mockImplementation.getAddress(), + ); + + await upgradeTx( + provider, + account, + teeValidator, + mockImplementation, + keyPair, + ); + + expect(await account.implementation()).to.eq( + await mockImplementation.getAddress(), + ); + }); + + it('should revert upgrading to the same implementation', async () => { + expect(await account.implementation()).to.be.eq( + await mockImplementation.getAddress(), + ); + + try { + await upgradeTx( + provider, + account, + teeValidator, + mockImplementation, + keyPair, + ); + assert(false, 'Should revert'); + } catch (err) {} + }); + }); +}); diff --git a/new-test/utils/managers/upgrademanager.ts b/new-test/utils/managers/upgrademanager.ts new file mode 100644 index 0000000..2e07178 --- /dev/null +++ b/new-test/utils/managers/upgrademanager.ts @@ -0,0 +1,33 @@ +/** + * Copyright Clave - All Rights Reserved + * Unauthorized copying of this file, via any medium is strictly prohibited + * Proprietary and confidential + */ +import type { ec } from 'elliptic'; +import type { Contract, Provider } from 'zksync-ethers'; +import { utils } from 'zksync-ethers'; + +import { prepareTeeTx } from '../transactions'; + +export async function upgradeTx( + provider: Provider, + account: Contract, + validator: Contract, + newImplementation: Contract, + keyPair: ec.KeyPair, +): Promise { + const upgradeTx = await account.upgradeTo.populateTransaction( + await newImplementation.getAddress(), + ); + const tx = await prepareTeeTx( + provider, + account, + upgradeTx, + await validator.getAddress(), + keyPair, + ); + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + await txReceipt.wait(); +} From 021b8253e4a45df012426540f8b085c4db4ed014 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Sat, 14 Sep 2024 22:45:11 +0300 Subject: [PATCH 060/103] [clave-contracts] chore: organize the imports --- new-test/utils/managers/ownermanager.ts | 3 +-- new-test/utils/managers/validatormanager.ts | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/new-test/utils/managers/ownermanager.ts b/new-test/utils/managers/ownermanager.ts index 303d2e4..bac1872 100644 --- a/new-test/utils/managers/ownermanager.ts +++ b/new-test/utils/managers/ownermanager.ts @@ -4,9 +4,8 @@ * Proprietary and confidential */ import type { ec } from 'elliptic'; -import type { Provider } from 'zksync-ethers'; +import type { Contract, Provider } from 'zksync-ethers'; import { utils } from 'zksync-ethers'; -import type { Contract } from 'zksync-ethers'; import { prepareTeeTx } from '../transactions'; diff --git a/new-test/utils/managers/validatormanager.ts b/new-test/utils/managers/validatormanager.ts index c143c0e..f6a6ecd 100644 --- a/new-test/utils/managers/validatormanager.ts +++ b/new-test/utils/managers/validatormanager.ts @@ -4,8 +4,7 @@ * Proprietary and confidential */ import type { ec } from 'elliptic'; -import type { Provider } from 'zksync-ethers'; -import type { Contract } from 'zksync-ethers'; +import type { Contract, Provider } from 'zksync-ethers'; import { utils } from 'zksync-ethers'; import { prepareTeeTx } from '../transactions'; From 693c2739eb7115ec429c40bf7438ff3ca6abc4bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Sat, 14 Sep 2024 23:45:51 +0300 Subject: [PATCH 061/103] [clave-contracts] feat: hook manager tests --- .../accounts/managers/hookmanager.test.ts | 147 ++++++++++++++++++ new-test/utils/managers/hookmanager.ts | 61 ++++++++ new-test/utils/names.ts | 5 + 3 files changed, 213 insertions(+) create mode 100644 new-test/accounts/managers/hookmanager.test.ts create mode 100644 new-test/utils/managers/hookmanager.ts diff --git a/new-test/accounts/managers/hookmanager.test.ts b/new-test/accounts/managers/hookmanager.test.ts new file mode 100644 index 0000000..a63112c --- /dev/null +++ b/new-test/accounts/managers/hookmanager.test.ts @@ -0,0 +1,147 @@ +/** + * Copyright Clave - All Rights Reserved + * Unauthorized copying of this file, via any medium is strictly prohibited + * Proprietary and confidential + */ +import { expect } from 'chai'; +import type { ec } from 'elliptic'; +import * as hre from 'hardhat'; +import type { Contract, Wallet } from 'zksync-ethers'; +import { Provider } from 'zksync-ethers'; + +import { LOCAL_RICH_WALLETS, getWallet } from '../../../deploy/utils'; +import { ClaveDeployer } from '../../utils/deployer'; +import { fixture } from '../../utils/fixture'; +import { addHook, removeHook } from '../../utils/managers/hookmanager'; +import { HOOKS, VALIDATORS } from '../../utils/names'; + +describe('Clave Contracts - Manager tests', () => { + let deployer: ClaveDeployer; + let provider: Provider; + let richWallet: Wallet; + let teeValidator: Contract; + let account: Contract; + let keyPair: ec.KeyPair; + + before(async () => { + richWallet = getWallet(hre, LOCAL_RICH_WALLETS[0].privateKey); + deployer = new ClaveDeployer(hre, richWallet); + provider = new Provider(hre.network.config.url, undefined, { + cacheTimeout: -1, + }); + + [, , , , teeValidator, account, keyPair] = await fixture( + deployer, + VALIDATORS.TEE, + ); + + const accountAddress = await account.getAddress(); + + await deployer.fund(10000, accountAddress); + }); + + describe('Hook Manager', () => { + it('should check existing modules', async () => { + expect(await account.listHooks(HOOKS.VALIDATION)).to.deep.eq([]); + expect(await account.listHooks(HOOKS.EXECUTION)).to.deep.eq([]); + }); + + describe('Validation hooks', async () => { + let validationHook: Contract; + + it('should add a validation hook', async () => { + validationHook = await deployer.deployCustomContract( + 'MockValidationHook', + [], + ); + expect(await account.isHook(await validationHook.getAddress())) + .to.be.false; + + await addHook( + provider, + account, + teeValidator, + validationHook, + HOOKS.VALIDATION, + keyPair, + ); + + expect(await account.isHook(await validationHook.getAddress())) + .to.be.true; + + const expectedHooks = [await validationHook.getAddress()]; + expect(await account.listHooks(HOOKS.VALIDATION)).to.deep.eq( + expectedHooks, + ); + }); + + it('should remove a validation hook', async () => { + expect(await account.isHook(await validationHook.getAddress())) + .to.be.true; + + await removeHook( + provider, + account, + teeValidator, + validationHook, + HOOKS.VALIDATION, + keyPair, + ); + expect(await account.isHook(await validationHook.getAddress())) + .to.be.false; + + expect(await account.listHooks(HOOKS.VALIDATION)).to.deep.eq( + [], + ); + }); + }); + + describe('Execution hooks', async () => { + let executionHook: Contract; + + it('should add a execution hook', async () => { + executionHook = await deployer.deployCustomContract( + 'MockExecutionHook', + [], + ); + expect(await account.isHook(await executionHook.getAddress())) + .to.be.false; + + await addHook( + provider, + account, + teeValidator, + executionHook, + HOOKS.EXECUTION, + keyPair, + ); + + expect(await account.isHook(await executionHook.getAddress())) + .to.be.true; + + const expectedHooks = [await executionHook.getAddress()]; + expect(await account.listHooks(HOOKS.EXECUTION)).to.deep.eq( + expectedHooks, + ); + }); + + it('should remove a execution hook', async () => { + expect(await account.isHook(await executionHook.getAddress())) + .to.be.true; + + await removeHook( + provider, + account, + teeValidator, + executionHook, + HOOKS.EXECUTION, + keyPair, + ); + expect(await account.isHook(await executionHook.getAddress())) + .to.be.false; + + expect(await account.listHooks(HOOKS.EXECUTION)).to.deep.eq([]); + }); + }); + }); +}); diff --git a/new-test/utils/managers/hookmanager.ts b/new-test/utils/managers/hookmanager.ts new file mode 100644 index 0000000..19d4f43 --- /dev/null +++ b/new-test/utils/managers/hookmanager.ts @@ -0,0 +1,61 @@ +/** + * Copyright Clave - All Rights Reserved + * Unauthorized copying of this file, via any medium is strictly prohibited + * Proprietary and confidential + */ +import type { ec } from 'elliptic'; +import type { Contract, Provider } from 'zksync-ethers'; +import { utils } from 'zksync-ethers'; + +import type { HOOKS } from '../names'; +import { prepareTeeTx } from '../transactions'; + +export async function addHook( + provider: Provider, + account: Contract, + validator: Contract, + hook: Contract, + isValidation: HOOKS, + keyPair: ec.KeyPair, +): Promise { + const addHookTx = await account.addHook.populateTransaction( + await hook.getAddress(), + isValidation == 1 ? true : false, + ); + const tx = await prepareTeeTx( + provider, + account, + addHookTx, + await validator.getAddress(), + keyPair, + ); + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + await txReceipt.wait(); +} + +export async function removeHook( + provider: Provider, + account: Contract, + validator: Contract, + hook: Contract, + isValidation: HOOKS, + keyPair: ec.KeyPair, +): Promise { + const removeHookTx = await account.removeHook.populateTransaction( + await hook.getAddress(), + isValidation == 1 ? true : false, + ); + const tx = await prepareTeeTx( + provider, + account, + removeHookTx, + await validator.getAddress(), + keyPair, + ); + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + await txReceipt.wait(); +} diff --git a/new-test/utils/names.ts b/new-test/utils/names.ts index ec2b5ff..9a7030f 100644 --- a/new-test/utils/names.ts +++ b/new-test/utils/names.ts @@ -19,3 +19,8 @@ export enum VALIDATORS { EOA = 'EOAValidator', PASSKEY = 'PasskeyValidator', } + +export enum HOOKS { + VALIDATION = 1, + EXECUTION = 0, +} From 3bddfe0d3c73803e2ca37f4b9390f24ef6e2876c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Sun, 15 Sep 2024 00:50:15 +0300 Subject: [PATCH 062/103] [clave-contracts] chore: incorrect naming --- new-test/accounts/managers/hookmanager.test.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/new-test/accounts/managers/hookmanager.test.ts b/new-test/accounts/managers/hookmanager.test.ts index a63112c..53ac80e 100644 --- a/new-test/accounts/managers/hookmanager.test.ts +++ b/new-test/accounts/managers/hookmanager.test.ts @@ -41,7 +41,7 @@ describe('Clave Contracts - Manager tests', () => { }); describe('Hook Manager', () => { - it('should check existing modules', async () => { + it('should check existing hooks', async () => { expect(await account.listHooks(HOOKS.VALIDATION)).to.deep.eq([]); expect(await account.listHooks(HOOKS.EXECUTION)).to.deep.eq([]); }); @@ -49,11 +49,14 @@ describe('Clave Contracts - Manager tests', () => { describe('Validation hooks', async () => { let validationHook: Contract; - it('should add a validation hook', async () => { + before(async () => { validationHook = await deployer.deployCustomContract( 'MockValidationHook', [], ); + }); + + it('should add a validation hook', async () => { expect(await account.isHook(await validationHook.getAddress())) .to.be.false; @@ -99,11 +102,14 @@ describe('Clave Contracts - Manager tests', () => { describe('Execution hooks', async () => { let executionHook: Contract; - it('should add a execution hook', async () => { + before(async () => { executionHook = await deployer.deployCustomContract( 'MockExecutionHook', [], ); + }); + + it('should add a execution hook', async () => { expect(await account.isHook(await executionHook.getAddress())) .to.be.false; From c87616e5d34f4d0a2bb2523986bb7179c5dabd4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Sun, 15 Sep 2024 16:34:33 +0300 Subject: [PATCH 063/103] [clave-contracts] feat: fix and improve hook manager tests --- .../accounts/managers/hookmanager.test.ts | 295 +++++++++++++++++- new-test/utils/managers/hookmanager.ts | 5 + 2 files changed, 297 insertions(+), 3 deletions(-) diff --git a/new-test/accounts/managers/hookmanager.test.ts b/new-test/accounts/managers/hookmanager.test.ts index 53ac80e..2162814 100644 --- a/new-test/accounts/managers/hookmanager.test.ts +++ b/new-test/accounts/managers/hookmanager.test.ts @@ -3,17 +3,19 @@ * Unauthorized copying of this file, via any medium is strictly prohibited * Proprietary and confidential */ -import { expect } from 'chai'; +import { assert, expect } from 'chai'; import type { ec } from 'elliptic'; +import { AbiCoder, randomBytes, solidityPackedKeccak256 } from 'ethers'; import * as hre from 'hardhat'; -import type { Contract, Wallet } from 'zksync-ethers'; -import { Provider } from 'zksync-ethers'; +import { Contract } from 'zksync-ethers'; +import { Provider, Wallet, utils } from 'zksync-ethers'; import { LOCAL_RICH_WALLETS, getWallet } from '../../../deploy/utils'; import { ClaveDeployer } from '../../utils/deployer'; import { fixture } from '../../utils/fixture'; import { addHook, removeHook } from '../../utils/managers/hookmanager'; import { HOOKS, VALIDATORS } from '../../utils/names'; +import { ethTransfer, prepareTeeTx } from '../../utils/transactions'; describe('Clave Contracts - Manager tests', () => { let deployer: ClaveDeployer; @@ -78,10 +80,54 @@ describe('Clave Contracts - Manager tests', () => { ); }); + it('should set hook data correctly', async () => { + const key = randomBytes(32); + const data = '0xc1ae'; + + await validationHook.setHookData( + await account.getAddress(), + key, + data, + ); + + expect( + await account.getHookData( + await validationHook.getAddress(), + key, + ), + ).to.eq(data); + }); + + it('should run validation hooks succcesfully', async () => { + const transfer = ethTransfer(await richWallet.getAddress(), 1); + + const hookData = [ + AbiCoder.defaultAbiCoder().encode(['bool'], [false]), + ]; + + const tx = await prepareTeeTx( + provider, + account, + transfer, + await teeValidator.getAddress(), + keyPair, + hookData, + ); + + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + await txReceipt.wait(); + }); + it('should remove a validation hook', async () => { expect(await account.isHook(await validationHook.getAddress())) .to.be.true; + const hookData = [ + AbiCoder.defaultAbiCoder().encode(['bool'], [false]), + ]; + await removeHook( provider, account, @@ -89,6 +135,7 @@ describe('Clave Contracts - Manager tests', () => { validationHook, HOOKS.VALIDATION, keyPair, + hookData, ); expect(await account.isHook(await validationHook.getAddress())) .to.be.false; @@ -149,5 +196,247 @@ describe('Clave Contracts - Manager tests', () => { expect(await account.listHooks(HOOKS.EXECUTION)).to.deep.eq([]); }); }); + + describe('Common execution and validation hook tests', async () => { + let validationHook: Contract; + let executionHook: Contract; + + before(async () => { + validationHook = await deployer.deployCustomContract( + 'MockValidationHook', + [], + ); + executionHook = await deployer.deployCustomContract( + 'MockExecutionHook', + [], + ); + }); + + it('should revert adding a hook with unauthorized msg.sender', async () => { + expect(await account.isHook(await validationHook.getAddress())) + .to.be.false; + expect(await account.isHook(await executionHook.getAddress())) + .to.be.false; + + await expect( + account.addHook( + await validationHook.getAddress(), + HOOKS.VALIDATION, + ), + ).to.be.revertedWithCustomError( + account, + 'NOT_FROM_SELF_OR_MODULE', + ); + + await expect( + account.addHook( + await executionHook.getAddress(), + HOOKS.EXECUTION, + ), + ).to.be.revertedWithCustomError( + account, + 'NOT_FROM_SELF_OR_MODULE', + ); + }); + + it('should revert adding a hook with NO interface', async () => { + const noInterfaceHook = Wallet.createRandom(); + expect(await account.isHook(await validationHook.getAddress())) + .to.be.false; + + try { + await addHook( + provider, + account, + teeValidator, + new Contract(await noInterfaceHook.getAddress(), []), + HOOKS.VALIDATION, + keyPair, + ); + assert(false, 'Should revert'); + } catch (err) {} + + try { + await addHook( + provider, + account, + teeValidator, + new Contract(await noInterfaceHook.getAddress(), []), + HOOKS.EXECUTION, + keyPair, + ); + assert(false, 'Should revert'); + } catch (err) {} + }); + + it('should revert adding a hook with invalid hookAndData length', async () => { + const hookAndData = (await validationHook.getAddress()).slice( + 0, + 10, + ); + const addHookTx = await account.addHook.populateTransaction( + hookAndData, + HOOKS.VALIDATION, + ); + + const tx = await prepareTeeTx( + provider, + account, + addHookTx, + await teeValidator.getAddress(), + keyPair, + ); + + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + try { + await txReceipt.wait(); + assert(false, 'Should revert'); + } catch (err) {} + }); + + describe('Added hooks failure tests', async () => { + before(async () => { + await addHook( + provider, + account, + teeValidator, + validationHook, + HOOKS.VALIDATION, + keyPair, + ); + + const key = randomBytes(32); + const data = '0xc1ae'; + await validationHook.setHookData( + await account.getAddress(), + key, + data, + ); + + const hookData = [ + AbiCoder.defaultAbiCoder().encode(['bool'], [false]), + ]; + await addHook( + provider, + account, + teeValidator, + executionHook, + HOOKS.EXECUTION, + keyPair, + hookData, + ); + }); + + beforeEach(async () => { + expect( + await account.isHook(await validationHook.getAddress()), + ).to.be.true; + expect( + await account.isHook(await executionHook.getAddress()), + ).to.be.true; + }); + + it('should revert removing hooks with unauthorized msg.sender', async () => { + await expect( + account.removeHook( + await validationHook.getAddress(), + HOOKS.VALIDATION, + ), + ).to.be.revertedWithCustomError( + account, + 'NOT_FROM_SELF_OR_MODULE', + ); + await expect( + account.removeHook( + await executionHook.getAddress(), + HOOKS.EXECUTION, + ), + ).to.be.revertedWithCustomError( + account, + 'NOT_FROM_SELF_OR_MODULE', + ); + }); + + it('should revert when validation hooks failed', async () => { + const transfer = ethTransfer( + await richWallet.getAddress(), + 5, + ); + const hookData = [ + AbiCoder.defaultAbiCoder().encode(['bool'], [true]), + ]; + const tx = await prepareTeeTx( + provider, + account, + transfer, + await teeValidator.getAddress(), + keyPair, + hookData, + ); + + try { + await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + assert(false, 'Should revert'); + } catch (e) {} + }); + + it('should revert when execution hooks failed', async () => { + const transfer = ethTransfer( + await richWallet.getAddress(), + 5, + ); + const hookData = [ + AbiCoder.defaultAbiCoder().encode(['bool'], [false]), + ]; + + const tx = await prepareTeeTx( + provider, + account, + transfer, + await teeValidator.getAddress(), + keyPair, + hookData, + ); + + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + + try { + await txReceipt.wait(); + assert(false, 'Should revert'); + } catch (err) {} + }); + + it('should revert setting hook data with unauthorized msg.sender, not from hooks', async function () { + const key = randomBytes(32); + const data = '0xc1ae'; + + await expect( + account.setHookData(key, data), + ).to.be.revertedWithCustomError(account, 'NOT_FROM_HOOK'); + }); + + it('should revert setting hook data with invalid key', async function () { + const key = solidityPackedKeccak256( + ['string'], + ['HookManager.context'], + ); + const data = '0xc1ae'; + + await expect( + validationHook.setHookData( + await account.getAddress(), + key, + data, + ), + ).to.be.revertedWithCustomError(account, 'INVALID_KEY'); + }); + }); + }); }); }); diff --git a/new-test/utils/managers/hookmanager.ts b/new-test/utils/managers/hookmanager.ts index 19d4f43..18faa6a 100644 --- a/new-test/utils/managers/hookmanager.ts +++ b/new-test/utils/managers/hookmanager.ts @@ -4,6 +4,7 @@ * Proprietary and confidential */ import type { ec } from 'elliptic'; +import type { BytesLike } from 'ethers'; import type { Contract, Provider } from 'zksync-ethers'; import { utils } from 'zksync-ethers'; @@ -17,6 +18,7 @@ export async function addHook( hook: Contract, isValidation: HOOKS, keyPair: ec.KeyPair, + hookData: Array = [], ): Promise { const addHookTx = await account.addHook.populateTransaction( await hook.getAddress(), @@ -28,6 +30,7 @@ export async function addHook( addHookTx, await validator.getAddress(), keyPair, + hookData, ); const txReceipt = await provider.broadcastTransaction( utils.serializeEip712(tx), @@ -42,6 +45,7 @@ export async function removeHook( hook: Contract, isValidation: HOOKS, keyPair: ec.KeyPair, + hookData: Array = [], ): Promise { const removeHookTx = await account.removeHook.populateTransaction( await hook.getAddress(), @@ -53,6 +57,7 @@ export async function removeHook( removeHookTx, await validator.getAddress(), keyPair, + hookData, ); const txReceipt = await provider.broadcastTransaction( utils.serializeEip712(tx), From 7879a948308c69dc328ebe7cf39d7409465d4966 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Sun, 15 Sep 2024 16:49:35 +0300 Subject: [PATCH 064/103] [clave-contracts] chore: branch test --- new-test/paymasters/erc20paymaster.test.ts | 5 +++++ new-test/paymasters/gaslesspaymaster.test.ts | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 new-test/paymasters/erc20paymaster.test.ts create mode 100644 new-test/paymasters/gaslesspaymaster.test.ts diff --git a/new-test/paymasters/erc20paymaster.test.ts b/new-test/paymasters/erc20paymaster.test.ts new file mode 100644 index 0000000..4ddd6b2 --- /dev/null +++ b/new-test/paymasters/erc20paymaster.test.ts @@ -0,0 +1,5 @@ +/** + * Copyright Clave - All Rights Reserved + * Unauthorized copying of this file, via any medium is strictly prohibited + * Proprietary and confidential + */ diff --git a/new-test/paymasters/gaslesspaymaster.test.ts b/new-test/paymasters/gaslesspaymaster.test.ts new file mode 100644 index 0000000..4ddd6b2 --- /dev/null +++ b/new-test/paymasters/gaslesspaymaster.test.ts @@ -0,0 +1,5 @@ +/** + * Copyright Clave - All Rights Reserved + * Unauthorized copying of this file, via any medium is strictly prohibited + * Proprietary and confidential + */ From c44a946559a4ae075be280d7811bbd28cc070fa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Sun, 15 Sep 2024 18:24:01 +0300 Subject: [PATCH 065/103] [clave-contracts] feat: add gasless paymaster tests --- new-test/paymasters/gaslesspaymaster.test.ts | 407 +++++++++++++++++++ new-test/utils/deployer.ts | 32 +- new-test/utils/names.ts | 6 + new-test/utils/paymasters.ts | 30 ++ 4 files changed, 474 insertions(+), 1 deletion(-) create mode 100644 new-test/utils/paymasters.ts diff --git a/new-test/paymasters/gaslesspaymaster.test.ts b/new-test/paymasters/gaslesspaymaster.test.ts index 4ddd6b2..0f68df1 100644 --- a/new-test/paymasters/gaslesspaymaster.test.ts +++ b/new-test/paymasters/gaslesspaymaster.test.ts @@ -3,3 +3,410 @@ * Unauthorized copying of this file, via any medium is strictly prohibited * Proprietary and confidential */ +import { assert, expect } from 'chai'; +import { parseEther } from 'ethers'; +import * as hre from 'hardhat'; +import type { Contract, Wallet } from 'zksync-ethers'; +import { Provider, utils } from 'zksync-ethers'; + +import { LOCAL_RICH_WALLETS, getWallet } from '../../deploy/utils'; +import type { CallStruct } from '../../typechain-types/contracts/batch/BatchCaller'; +import { ClaveDeployer } from '../utils/deployer'; +import { fixture } from '../utils/fixture'; +import { PAYMASTERS } from '../utils/names'; +import { getGaslessPaymasterInput } from '../utils/paymasters'; +import { + ethTransfer, + prepareMockBatchTx, + prepareMockTx, +} from '../utils/transactions'; + +describe('Clave Contracts - Paymaster tests', () => { + let deployer: ClaveDeployer; + let provider: Provider; + let richWallet: Wallet; + let batchCaller: Contract; + let registry: Contract; + let mockValidator: Contract; + let account: Contract; + + let gaslessPaymaster: Contract; + let erc20: Contract; + + before(async () => { + richWallet = getWallet(hre, LOCAL_RICH_WALLETS[0].privateKey); + deployer = new ClaveDeployer(hre, richWallet); + provider = new Provider(hre.network.config.url, undefined, { + cacheTimeout: -1, + }); + + [batchCaller, registry, , , mockValidator, account] = await fixture( + deployer, + ); + + const accountAddress = await account.getAddress(); + + await deployer.fund(10000, accountAddress); + + erc20 = await deployer.deployCustomContract('MockStable', []); + await erc20.mint(accountAddress, parseEther('100000')); + + gaslessPaymaster = await deployer.paymaster(PAYMASTERS.GASLESS, { + gasless: [await registry.getAddress(), 3], + }); + + await deployer.fund(50, await gaslessPaymaster.getAddress()); + }); + + it('Should fund the paymaster and account', async () => { + expect( + await provider.getBalance(await gaslessPaymaster.getAddress()), + ).to.eq(parseEther('50')); + + expect(await provider.getBalance(await account.getAddress())).to.eq( + parseEther('10000'), + ); + + expect(await erc20.balanceOf(await account.getAddress())).to.be.eq( + parseEther('100000'), + ); + }); + + describe('Gasless Paymaster', () => { + let accountAddress: string; + let richAddress: string; + let paymasterAddress: string; + + let accountBalanceBefore: bigint; + let richBalanceBefore: bigint; + let paymasterBalanceBefore: bigint; + + let paymasterUserLimit: bigint; + + before(async () => { + const addresses = await Promise.all([ + account.getAddress(), + richWallet.getAddress(), + gaslessPaymaster.getAddress(), + ]); + [accountAddress, richAddress, paymasterAddress] = addresses; + }); + + beforeEach(async () => { + const balances = await Promise.all([ + provider.getBalance(accountAddress), + provider.getBalance(richAddress), + provider.getBalance(paymasterAddress), + ]); + [accountBalanceBefore, richBalanceBefore, paymasterBalanceBefore] = + balances; + + paymasterUserLimit = await gaslessPaymaster.getRemainingUserLimit( + accountAddress, + ); + }); + + it('should send ETH and do not pay gas', async () => { + const amount = parseEther('1'); + + const txData = ethTransfer(richAddress, amount); + const tx = await prepareMockTx( + provider, + account, + txData, + await mockValidator.getAddress(), + getGaslessPaymasterInput(paymasterAddress), + ); + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + await txReceipt.wait(); + + const [accountBalanceAfter, richBalanceAfter] = await Promise.all([ + provider.getBalance(accountAddress), + provider.getBalance(richAddress), + ]); + + expect(accountBalanceAfter).to.be.eq(accountBalanceBefore - amount); + expect(richBalanceAfter).to.be.equal(richBalanceBefore + amount); + + // Gas payment check + const gaslessPaymasterBalanceAfter = await provider.getBalance( + await gaslessPaymaster.getAddress(), + ); + + expect(gaslessPaymasterBalanceAfter).is.lessThan( + paymasterBalanceBefore, + ); + + const newPaymasterUserLimit: bigint = + await gaslessPaymaster.getRemainingUserLimit(accountAddress); + expect(paymasterUserLimit).to.be.eq( + newPaymasterUserLimit + BigInt(1), + ); + }); + + it('should send ERC20 token / contract interaction and do not pay gas', async () => { + const amount = parseEther('100'); + + const [accountERC20BalanceBefore, richERC20BalanceBefore] = + await Promise.all([ + erc20.balanceOf(accountAddress), + erc20.balanceOf(richAddress), + ]); + + const txData = { + to: await erc20.getAddress(), + value: 0, + data: erc20.interface.encodeFunctionData('transfer', [ + richAddress, + amount, + ]), + }; + const tx = await prepareMockTx( + provider, + account, + txData, + await mockValidator.getAddress(), + getGaslessPaymasterInput(paymasterAddress), + ); + + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + await txReceipt.wait(); + + const [accountERC20BalanceAfter, richERC20BalanceAfter] = + await Promise.all([ + erc20.balanceOf(accountAddress), + erc20.balanceOf(richAddress), + ]); + + expect(accountERC20BalanceAfter).to.be.equal( + accountERC20BalanceBefore - amount, + ); + expect(richERC20BalanceAfter).to.be.equal( + richERC20BalanceBefore + amount, + ); + + // Gas payment check + const accountBalanceAfter = await provider.getBalance( + accountAddress, + ); + expect(accountBalanceBefore).to.be.eq(accountBalanceAfter); + + const gaslessPaymasterBalanceAfter = await provider.getBalance( + await gaslessPaymaster.getAddress(), + ); + + expect(gaslessPaymasterBalanceAfter).is.lessThan( + paymasterBalanceBefore, + ); + + const newPaymasterUserLimit: bigint = + await gaslessPaymaster.getRemainingUserLimit(accountAddress); + expect(paymasterUserLimit).to.be.eq( + newPaymasterUserLimit + BigInt(1), + ); + }); + + it('should send batch tx / delegate call and do not pay gas', async () => { + const amount = parseEther('100'); + + const [accountERC20BalanceBefore, richERC20BalanceBefore] = + await Promise.all([ + erc20.balanceOf(accountAddress), + erc20.balanceOf(richAddress), + ]); + + const calls: Array = [ + { + target: richAddress, + allowFailure: false, + value: amount, + callData: '0x', + }, + { + target: await erc20.getAddress(), + allowFailure: false, + value: 0, + callData: erc20.interface.encodeFunctionData('transfer', [ + richAddress, + amount, + ]), + }, + ]; + + const batchTx = await prepareMockBatchTx( + provider, + account, + await batchCaller.getAddress(), + calls, + await mockValidator.getAddress(), + [], + getGaslessPaymasterInput(paymasterAddress), + ); + + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(batchTx), + ); + await txReceipt.wait(); + + const [accountBalanceAfter, richBalanceAfter] = await Promise.all([ + provider.getBalance(accountAddress), + provider.getBalance(richAddress), + ]); + + expect(accountBalanceAfter).to.be.eq(accountBalanceBefore - amount); + expect(richBalanceAfter).to.be.equal(richBalanceBefore + amount); + + const [accountERC20BalanceAfter, richERC20BalanceAfter] = + await Promise.all([ + erc20.balanceOf(accountAddress), + erc20.balanceOf(richAddress), + ]); + + expect(accountERC20BalanceAfter).to.be.equal( + accountERC20BalanceBefore - amount, + ); + expect(richERC20BalanceAfter).to.be.equal( + richERC20BalanceBefore + amount, + ); + + // Gas payment check + const gaslessPaymasterBalanceAfter = await provider.getBalance( + await gaslessPaymaster.getAddress(), + ); + + expect(gaslessPaymasterBalanceAfter).is.lessThan( + paymasterBalanceBefore, + ); + + const newPaymasterUserLimit: bigint = + await gaslessPaymaster.getRemainingUserLimit(accountAddress); + expect(paymasterUserLimit).to.be.eq( + newPaymasterUserLimit + BigInt(1), + ); + }); + + it('should revert if userLimit is reached', async () => { + expect(paymasterUserLimit).to.be.eq(BigInt(0)); + + const amount = parseEther('1'); + + const txData = ethTransfer(richAddress, amount); + const tx = await prepareMockTx( + provider, + account, + txData, + await mockValidator.getAddress(), + getGaslessPaymasterInput(paymasterAddress), + ); + + try { + await provider.broadcastTransaction(utils.serializeEip712(tx)); + assert(false, 'Should revert'); + } catch (error) {} + + const accountBalanceAfter = await provider.getBalance( + accountAddress, + ); + expect(accountBalanceAfter).to.be.eq(accountBalanceBefore); + }); + + it('should be able to increase user limit', async () => { + const newUserLimit = 4; + const tx = await gaslessPaymaster.updateUserLimit(newUserLimit); + await tx.wait(); + + const updatedUserLimit = await gaslessPaymaster.userLimit(); + expect(updatedUserLimit).to.be.eq(newUserLimit); + }); + + it('should send ETH and do not pay gas after increasing the user limit', async () => { + const amount = parseEther('1'); + + const txData = ethTransfer(richAddress, amount); + const tx = await prepareMockTx( + provider, + account, + txData, + await mockValidator.getAddress(), + getGaslessPaymasterInput(paymasterAddress), + ); + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + await txReceipt.wait(); + + const [accountBalanceAfter, richBalanceAfter] = await Promise.all([ + provider.getBalance(accountAddress), + provider.getBalance(richAddress), + ]); + + expect(accountBalanceAfter).to.be.eq(accountBalanceBefore - amount); + expect(richBalanceAfter).to.be.equal(richBalanceBefore + amount); + + // Gas payment check + const gaslessPaymasterBalanceAfter = await provider.getBalance( + await gaslessPaymaster.getAddress(), + ); + + expect(gaslessPaymasterBalanceAfter).is.lessThan( + paymasterBalanceBefore, + ); + + const newPaymasterUserLimit: bigint = + await gaslessPaymaster.getRemainingUserLimit(accountAddress); + expect(paymasterUserLimit).to.be.eq( + newPaymasterUserLimit + BigInt(1), + ); + }); + + it('should be able to add limitless addresses', async () => { + const tx = await gaslessPaymaster.addLimitlessAddresses([ + accountAddress, + ]); + await tx.wait(); + + expect(await gaslessPaymaster.limitlessAddresses(accountAddress)).to + .be.true; + }); + + it('should send ETH and do not pay gas after being limitless address', async () => { + expect(paymasterUserLimit).to.be.eq(BigInt(0)); + + const amount = parseEther('1'); + + const txData = ethTransfer(richAddress, amount); + const tx = await prepareMockTx( + provider, + account, + txData, + await mockValidator.getAddress(), + getGaslessPaymasterInput(paymasterAddress), + ); + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + await txReceipt.wait(); + + const [accountBalanceAfter, richBalanceAfter] = await Promise.all([ + provider.getBalance(accountAddress), + provider.getBalance(richAddress), + ]); + + expect(accountBalanceAfter).to.be.eq(accountBalanceBefore - amount); + expect(richBalanceAfter).to.be.equal(richBalanceBefore + amount); + + // Gas payment check + const gaslessPaymasterBalanceAfter = await provider.getBalance( + await gaslessPaymaster.getAddress(), + ); + + expect(gaslessPaymasterBalanceAfter).is.lessThan( + paymasterBalanceBefore, + ); + }); + }); +}); diff --git a/new-test/utils/deployer.ts b/new-test/utils/deployer.ts index 6322e1a..7cf7fb3 100644 --- a/new-test/utils/deployer.ts +++ b/new-test/utils/deployer.ts @@ -11,7 +11,7 @@ import { Contract, utils } from 'zksync-ethers'; import { deployContract, getWallet } from '../../deploy/utils'; import type { CallStruct } from '../../typechain-types/contracts/batch/BatchCaller'; -import { CONTRACT_NAMES, type VALIDATORS } from './names'; +import { CONTRACT_NAMES, PAYMASTERS, type VALIDATORS } from './names'; import { encodePublicKey } from './p256'; // This class helps deploy Clave contracts for the tests @@ -196,6 +196,36 @@ export class ClaveDeployer { return account; } + public async paymaster( + name: PAYMASTERS, + config: { + gasless?: [registryAddress: string, limit: number]; + erc20?: Array<{ + tokenAddress: string; + decimals: number; + priceMarkup: number; + }>; + }, + ): Promise { + if ( + (name === PAYMASTERS.GASLESS && !config.gasless) || + (name === PAYMASTERS.ERC20 && !config.erc20) || + (name === PAYMASTERS.ERC20_MOCK && !config.erc20) + ) { + throw new Error('Config mismatch.'); + } + + return await deployContract( + this.hre, + name, + name == PAYMASTERS.GASLESS ? config.gasless : [config.erc20], + { + wallet: this.deployerWallet, + silent: true, + }, + ); + } + public async fund( ethAmount: number, accountAddress: string, diff --git a/new-test/utils/names.ts b/new-test/utils/names.ts index 9a7030f..c7f3192 100644 --- a/new-test/utils/names.ts +++ b/new-test/utils/names.ts @@ -24,3 +24,9 @@ export enum HOOKS { VALIDATION = 1, EXECUTION = 0, } + +export enum PAYMASTERS { + GASLESS = 'GaslessPaymaster', + ERC20 = 'ERC20Paymaster', + ERC20_MOCK = 'ERC20PaymasterMock', +} diff --git a/new-test/utils/paymasters.ts b/new-test/utils/paymasters.ts new file mode 100644 index 0000000..9912045 --- /dev/null +++ b/new-test/utils/paymasters.ts @@ -0,0 +1,30 @@ +/** + * Copyright Clave - All Rights Reserved + * Unauthorized copying of this file, via any medium is strictly prohibited + * Proprietary and confidential + */ +import type { ethers } from 'ethers'; +import { type types, utils } from 'zksync-ethers'; + +export function getGaslessPaymasterInput( + paymasterAddress: types.Address, +): types.PaymasterParams { + return utils.getPaymasterParams(paymasterAddress, { + type: 'General', + innerInput: new Uint8Array(), + }); +} + +export function getERC20PaymasterInput( + paymasterAddress: types.Address, + tokenAddress: types.Address, + minimalAllowance: bigint, + oraclePayload: ethers.BytesLike, +): types.PaymasterParams { + return utils.getPaymasterParams(paymasterAddress, { + type: 'ApprovalBased', + token: tokenAddress, + minimalAllowance, + innerInput: oraclePayload, + }); +} From 948b291b8437de9c0338613faebdf3ed0a1c4569 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Sun, 15 Sep 2024 19:23:46 +0300 Subject: [PATCH 066/103] [clave-contracts] feat: add erc20 paymaster tests --- new-test/paymasters/erc20paymaster.test.ts | 279 +++++++++++++++++++++ new-test/utils/paymasters.ts | 10 +- 2 files changed, 288 insertions(+), 1 deletion(-) diff --git a/new-test/paymasters/erc20paymaster.test.ts b/new-test/paymasters/erc20paymaster.test.ts index 4ddd6b2..32025b6 100644 --- a/new-test/paymasters/erc20paymaster.test.ts +++ b/new-test/paymasters/erc20paymaster.test.ts @@ -3,3 +3,282 @@ * Unauthorized copying of this file, via any medium is strictly prohibited * Proprietary and confidential */ +import { expect } from 'chai'; +import { parseEther } from 'ethers'; +import * as hre from 'hardhat'; +import type { Contract, Wallet } from 'zksync-ethers'; +import { Provider, utils } from 'zksync-ethers'; + +import { LOCAL_RICH_WALLETS, getWallet } from '../../deploy/utils'; +import type { CallStruct } from '../../typechain-types/contracts/batch/BatchCaller'; +import { ClaveDeployer } from '../utils/deployer'; +import { fixture } from '../utils/fixture'; +import { PAYMASTERS } from '../utils/names'; +import { getERC20PaymasterInput, getOraclePayload } from '../utils/paymasters'; +import { + ethTransfer, + prepareMockBatchTx, + prepareMockTx, +} from '../utils/transactions'; + +describe('Clave Contracts - Paymaster tests', () => { + let deployer: ClaveDeployer; + let provider: Provider; + let richWallet: Wallet; + let batchCaller: Contract; + let mockValidator: Contract; + let account: Contract; + + let erc20Paymaster: Contract; + let erc20: Contract; + + before(async () => { + richWallet = getWallet(hre, LOCAL_RICH_WALLETS[0].privateKey); + deployer = new ClaveDeployer(hre, richWallet); + provider = new Provider(hre.network.config.url, undefined, { + cacheTimeout: -1, + }); + + [batchCaller, , , , mockValidator, account] = await fixture(deployer); + + const accountAddress = await account.getAddress(); + + await deployer.fund(10000, accountAddress); + + erc20 = await deployer.deployCustomContract('MockStable', []); + await erc20.mint(accountAddress, parseEther('100000')); + + erc20Paymaster = await deployer.paymaster(PAYMASTERS.ERC20_MOCK, { + erc20: [ + { + tokenAddress: await erc20.getAddress(), + decimals: 18, + priceMarkup: 20000, + }, + ], + }); + + await deployer.fund(50, await erc20Paymaster.getAddress()); + }); + + it('Should fund the paymaster and account', async () => { + expect( + await provider.getBalance(await erc20Paymaster.getAddress()), + ).to.eq(parseEther('50')); + + expect(await provider.getBalance(await account.getAddress())).to.eq( + parseEther('10000'), + ); + + expect(await erc20.balanceOf(await account.getAddress())).to.be.eq( + parseEther('100000'), + ); + }); + + describe('ERC20 Paymaster (Mocked Oracle)', () => { + let accountAddress: string; + let richAddress: string; + let paymasterAddress: string; + + let accountBalanceBefore: bigint; + let richBalanceBefore: bigint; + let paymasterBalanceBefore: bigint; + let accountERC20BalanceBefore: bigint; + let paymasterERC20BalanceBefore: bigint; + + before(async () => { + const addresses = await Promise.all([ + account.getAddress(), + richWallet.getAddress(), + erc20Paymaster.getAddress(), + ]); + [accountAddress, richAddress, paymasterAddress] = addresses; + }); + + beforeEach(async () => { + const balances = await Promise.all([ + provider.getBalance(accountAddress), + provider.getBalance(richAddress), + provider.getBalance(paymasterAddress), + erc20.balanceOf(accountAddress), + erc20.balanceOf(paymasterAddress), + ]); + [ + accountBalanceBefore, + richBalanceBefore, + paymasterBalanceBefore, + accountERC20BalanceBefore, + paymasterERC20BalanceBefore, + ] = balances; + }); + + it('should send ETH and pay gas with erc20 token', async () => { + const amount = parseEther('1'); + + const txData = ethTransfer(richAddress, amount); + const tx = await prepareMockTx( + provider, + account, + txData, + await mockValidator.getAddress(), + getERC20PaymasterInput( + paymasterAddress, + await erc20.getAddress(), + parseEther('50'), + await getOraclePayload(erc20Paymaster), + ), + ); + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + await txReceipt.wait(); + + const [accountBalanceAfter, richBalanceAfter] = await Promise.all([ + provider.getBalance(accountAddress), + provider.getBalance(richAddress), + ]); + + expect(accountBalanceAfter).to.be.eq(accountBalanceBefore - amount); + expect(richBalanceAfter).to.be.equal(richBalanceBefore + amount); + + // Gas payment check + const gaslessPaymasterBalanceAfter = await provider.getBalance( + await erc20Paymaster.getAddress(), + ); + + expect(gaslessPaymasterBalanceAfter).is.lessThan( + paymasterBalanceBefore, + ); + + const [accountERC20BalanceAfter, paymasterERC20BalanceAfter] = + await Promise.all([ + erc20.balanceOf(accountAddress), + erc20.balanceOf(paymasterAddress), + ]); + + expect( + accountERC20BalanceBefore + paymasterERC20BalanceBefore, + ).to.be.equal( + accountERC20BalanceAfter + paymasterERC20BalanceAfter, + ); + }); + + it('should send ERC20 token / contract interaction and pay gas with erc20 token', async () => { + const amount = parseEther('100'); + + const richERC20BalanceBefore = await erc20.balanceOf(richAddress); + + const txData = { + to: await erc20.getAddress(), + value: 0, + data: erc20.interface.encodeFunctionData('transfer', [ + richAddress, + amount, + ]), + }; + const tx = await prepareMockTx( + provider, + account, + txData, + await mockValidator.getAddress(), + getERC20PaymasterInput( + paymasterAddress, + await erc20.getAddress(), + parseEther('50'), + await getOraclePayload(erc20Paymaster), + ), + ); + + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + await txReceipt.wait(); + + const richERC20BalanceAfter = await erc20.balanceOf(richAddress); + + expect(richERC20BalanceAfter).to.be.equal( + richERC20BalanceBefore + amount, + ); + + // Gas payment check + const accountBalanceAfter = await provider.getBalance( + accountAddress, + ); + expect(accountBalanceBefore).to.be.eq(accountBalanceAfter); + + const gaslessPaymasterBalanceAfter = await provider.getBalance( + await erc20Paymaster.getAddress(), + ); + expect(gaslessPaymasterBalanceAfter).is.lessThan( + paymasterBalanceBefore, + ); + }); + + it('should send batch tx / delegate call and pay gas with erc20 token', async () => { + const amount = parseEther('100'); + + const richERC20BalanceBefore = await erc20.balanceOf(richAddress); + + const calls: Array = [ + { + target: richAddress, + allowFailure: false, + value: amount, + callData: '0x', + }, + { + target: await erc20.getAddress(), + allowFailure: false, + value: 0, + callData: erc20.interface.encodeFunctionData('transfer', [ + richAddress, + amount, + ]), + }, + ]; + + const batchTx = await prepareMockBatchTx( + provider, + account, + await batchCaller.getAddress(), + calls, + await mockValidator.getAddress(), + [], + getERC20PaymasterInput( + paymasterAddress, + await erc20.getAddress(), + parseEther('50'), + await getOraclePayload(erc20Paymaster), + ), + ); + + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(batchTx), + ); + await txReceipt.wait(); + + const [accountBalanceAfter, richBalanceAfter] = await Promise.all([ + provider.getBalance(accountAddress), + provider.getBalance(richAddress), + ]); + + expect(accountBalanceAfter).to.be.eq(accountBalanceBefore - amount); + expect(richBalanceAfter).to.be.equal(richBalanceBefore + amount); + + const richERC20BalanceAfter = await erc20.balanceOf(richAddress); + + expect(richERC20BalanceAfter).to.be.equal( + richERC20BalanceBefore + amount, + ); + + // Gas payment check + const gaslessPaymasterBalanceAfter = await provider.getBalance( + await erc20Paymaster.getAddress(), + ); + + expect(gaslessPaymasterBalanceAfter).is.lessThan( + paymasterBalanceBefore, + ); + }); + }); +}); diff --git a/new-test/utils/paymasters.ts b/new-test/utils/paymasters.ts index 9912045..1dc3649 100644 --- a/new-test/utils/paymasters.ts +++ b/new-test/utils/paymasters.ts @@ -4,7 +4,8 @@ * Proprietary and confidential */ import type { ethers } from 'ethers'; -import { type types, utils } from 'zksync-ethers'; +import { utils } from 'zksync-ethers'; +import type { Contract, types } from 'zksync-ethers'; export function getGaslessPaymasterInput( paymasterAddress: types.Address, @@ -28,3 +29,10 @@ export function getERC20PaymasterInput( innerInput: oraclePayload, }); } + +export async function getOraclePayload( + paymasterContract: Contract, +): Promise { + paymasterContract; + return '0x'; +} From e1f75d14a9c275eb970440e7135920d9d988b95e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Sun, 15 Sep 2024 19:27:24 +0300 Subject: [PATCH 067/103] [clave-contracts] chore: remove old tests --- {test => new-test}/utils/snapshot.ts | 0 test/clave.test.ts | 2285 -------------------------- test/utils/p256.ts | 27 - test/utils/paymaster.ts | 30 - test/utils/transaction.ts | 155 -- 5 files changed, 2497 deletions(-) rename {test => new-test}/utils/snapshot.ts (100%) delete mode 100644 test/clave.test.ts delete mode 100644 test/utils/p256.ts delete mode 100644 test/utils/paymaster.ts delete mode 100644 test/utils/transaction.ts diff --git a/test/utils/snapshot.ts b/new-test/utils/snapshot.ts similarity index 100% rename from test/utils/snapshot.ts rename to new-test/utils/snapshot.ts diff --git a/test/clave.test.ts b/test/clave.test.ts deleted file mode 100644 index d934f10..0000000 --- a/test/clave.test.ts +++ /dev/null @@ -1,2285 +0,0 @@ -/** - * Copyright Clave - All Rights Reserved - * Unauthorized copying of this file, via any medium is strictly prohibited - * Proprietary and confidential - */ -import { assert, expect } from 'chai'; -import type { ec } from 'elliptic'; -import { - AbiCoder, - WeiPerEther, - ZeroAddress, - concat, - ethers, - parseEther, - randomBytes, - solidityPackedKeccak256, -} from 'ethers'; -import * as hre from 'hardhat'; -import { Contract, Provider, Wallet, utils } from 'zksync-ethers'; - -import { LOCAL_RICH_WALLETS, deployContract, getWallet } from '../deploy/utils'; -import type { CallStruct } from '../typechain-types/contracts/batch/BatchCaller'; -import { encodePublicKey, genKey } from './utils/p256'; -import { getGaslessPaymasterInput } from './utils/paymaster'; -import { ethTransfer, prepareBatchTx, prepareTeeTx } from './utils/transaction'; - -let provider: Provider; -let richWallet: Wallet; -let keyPair: ec.KeyPair; - -let batchCaller: Contract; -let mockValidator: Contract; -let implementation: Contract; -let factory: Contract; -let account: Contract; -let registry: Contract; - -beforeEach(async () => { - provider = new Provider(hre.network.config.url, undefined, { - cacheTimeout: -1, - }); - richWallet = getWallet(hre, LOCAL_RICH_WALLETS[0].privateKey); - keyPair = genKey(); - const publicKey = encodePublicKey(keyPair); - batchCaller = await deployContract(hre, 'BatchCaller', undefined, { - wallet: richWallet, - silent: true, - }); - mockValidator = await deployContract(hre, 'MockValidator', undefined, { - wallet: richWallet, - silent: true, - }); - implementation = await deployContract( - hre, - 'ClaveImplementation', - [await batchCaller.getAddress()], - { - wallet: richWallet, - silent: true, - }, - ); - registry = await deployContract(hre, 'ClaveRegistry', undefined, { - wallet: richWallet, - silent: true, - }); - - //TODO: WHY DOES THIS HELP - await deployContract( - hre, - 'ClaveProxy', - [await implementation.getAddress()], - { wallet: richWallet, silent: true }, - ); - - const accountArtifact = await hre.zksyncEthers.loadArtifact('ClaveProxy'); - const bytecodeHash = utils.hashBytecode(accountArtifact.bytecode); - factory = await deployContract( - hre, - 'AccountFactory', - [ - await implementation.getAddress(), - await registry.getAddress(), - bytecodeHash, - richWallet.address, - ], - { - wallet: richWallet, - silent: true, - }, - ); - await registry.setFactory(await factory.getAddress()); - - const salt = ethers.randomBytes(32); - const call: CallStruct = { - target: ZeroAddress, - allowFailure: false, - value: 0, - callData: '0x', - }; - - const abiCoder = ethers.AbiCoder.defaultAbiCoder(); - const initializer = - '0x77ba2e75' + - abiCoder - .encode( - [ - 'bytes', - 'address', - 'bytes[]', - 'tuple(address target,bool allowFailure,uint256 value,bytes calldata)', - ], - [ - publicKey, - await mockValidator.getAddress(), - [], - [call.target, call.allowFailure, call.value, call.callData], - ], - ) - .slice(2); - - const tx = await factory.deployAccount(salt, initializer); - await tx.wait(); - - const accountAddress = await factory.getAddressForSalt(salt); - account = new Contract( - accountAddress, - implementation.interface, - richWallet, - ); - // 100 ETH transfered to Account - await ( - await richWallet.sendTransaction({ - to: await account.getAddress(), - value: parseEther('100'), - }) - ).wait(); -}); - -describe('Account no module no hook TEE validator', function () { - describe('Owner manager', function () { - describe('Should not revert when', function () { - it('Adds a new k1 owner correctly', async function () { - const newAddress = await Wallet.createRandom().getAddress(); - - expect(await account.k1IsOwner(newAddress)).to.be.false; - - const addOwnerTx = await account.k1AddOwner.populateTransaction( - newAddress, - ); - - const tx = await prepareTeeTx( - provider, - account, - addOwnerTx, - await mockValidator.getAddress(), - keyPair, - ); - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - await txReceipt.wait(); - - expect(await account.k1IsOwner(newAddress)).to.be.true; - - const expectedOwners = [newAddress]; - - expect(await account.k1ListOwners()).to.deep.eq(expectedOwners); - }); - - it('Removes a k1 owner correctly', async function () { - const newAddress = await Wallet.createRandom().getAddress(); - - const addOwnerTx = await account.k1AddOwner.populateTransaction( - newAddress, - ); - - const tx = await prepareTeeTx( - provider, - account, - addOwnerTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - await txReceipt.wait(); - - expect(await account.k1IsOwner(newAddress)).to.be.true; - - const removeOwnerTx = - await account.k1RemoveOwner.populateTransaction(newAddress); - - const tx2 = await prepareTeeTx( - provider, - account, - removeOwnerTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt2 = await provider.broadcastTransaction( - utils.serializeEip712(tx2), - ); - await txReceipt2.wait(); - - expect(await account.k1IsOwner(newAddress)).to.be.false; - - const expectedOwners: Array = []; - - expect(await account.k1ListOwners()).to.deep.eq(expectedOwners); - }); - - it('Resets owners correctly', async function () { - const newAddress = await Wallet.createRandom().getAddress(); - - const addOwnerTx = await account.k1AddOwner.populateTransaction( - newAddress, - ); - - const tx = await prepareTeeTx( - provider, - account, - addOwnerTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - await txReceipt.wait(); - - expect(await account.k1IsOwner(newAddress)).to.be.true; - - const newKeyPair = genKey(); - const newPublicKey = encodePublicKey(newKeyPair); - - const resetOwnersTx = - await account.resetOwners.populateTransaction(newPublicKey); - - const tx2 = await prepareTeeTx( - provider, - account, - resetOwnersTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt2 = await provider.broadcastTransaction( - utils.serializeEip712(tx2), - ); - await txReceipt2.wait(); - - const expectedR1Owners = [newPublicKey]; - const expectedK1Owners: Array = []; - - expect(await account.r1ListOwners()).to.deep.eq( - expectedR1Owners, - ); - expect(await account.k1ListOwners()).to.deep.eq( - expectedK1Owners, - ); - }); - }); - - describe('Should revert when', function () { - it('Adds r1 owner with invalid length', async function () { - let invalidLength = Math.ceil(Math.random() * 200) * 2; - invalidLength = invalidLength === 128 ? 130 : invalidLength; - - const invalidPubkey = '0x' + 'C'.repeat(invalidLength); - - const addOwnerTx = await account.r1AddOwner.populateTransaction( - invalidPubkey, - ); - - const tx = await prepareTeeTx( - provider, - account, - addOwnerTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - - // await expect(txReceipt.wait()).to.be.revertedWithCustomError( - // account, - // 'INVALID_PUBKEY_LENGTH', - // ); - - try { - await txReceipt.wait(); - assert(false, 'Should revert'); - } catch (e) {} - }); - - it('Adds r1 owner with unauthorized msg.sender', async function () { - const newKeyPair = genKey(); - const newPublicKey = encodePublicKey(newKeyPair); - - await expect( - account.r1AddOwner(newPublicKey), - ).to.be.revertedWithCustomError( - account, - 'NOT_FROM_SELF_OR_MODULE', - ); - }); - - it('Adds await zero getAddress() as k1 owner', async function () { - const addOwnerTx = await account.k1AddOwner.populateTransaction( - ZeroAddress, - ); - - const tx = await prepareTeeTx( - provider, - account, - addOwnerTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - - // await expect(txReceipt.wait()).to.be.revertedWithCustomError( - // account, - // 'INVALID_ADDRESS', - // ); - - try { - await txReceipt.wait(); - assert(false, 'Should revert'); - } catch (e) {} - }); - - it('Adds k1 owner with unauthorized msg.sender', async function () { - const randomWallet = Wallet.createRandom().connect(provider); - - await expect( - account.k1AddOwner(await randomWallet.getAddress()), - ).to.be.revertedWithCustomError( - account, - 'NOT_FROM_SELF_OR_MODULE', - ); - }); - - it('Removes r1 owner with unauthorized msg.sender', async function () { - const newKeyPair = genKey(); - const newPublicKey = encodePublicKey(newKeyPair); - - const addOwnerTx = await account.r1AddOwner.populateTransaction( - newPublicKey, - ); - - const tx = await prepareTeeTx( - provider, - account, - addOwnerTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - await txReceipt.wait(); - - expect(await account.r1IsOwner(newPublicKey)).to.be.true; - - await expect( - account.r1RemoveOwner(newPublicKey), - ).to.be.revertedWithCustomError( - account, - 'NOT_FROM_SELF_OR_MODULE', - ); - }); - - it('Removes last r1 owner', async function () { - const removeOwnerTx = - await account.r1RemoveOwner.populateTransaction( - encodePublicKey(keyPair), - ); - - const tx = await prepareTeeTx( - provider, - account, - removeOwnerTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - - // await expect(txReceipt.wait()).to.be.revertedWithCustomError( - // account, - // 'EMPTY_R1_OWNERS', - // ); - - try { - await txReceipt.wait(); - assert(false, 'Should revert'); - } catch (e) {} - }); - - it('Removes k1 owner with unauthorized msg.sender', async function () { - const newAddress = await Wallet.createRandom().getAddress(); - - const addOwnerTx = await account.k1AddOwner.populateTransaction( - newAddress, - ); - - const tx = await prepareTeeTx( - provider, - account, - addOwnerTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - await txReceipt.wait(); - - expect(await account.k1IsOwner(newAddress)).to.be.true; - - const randomWallet = Wallet.createRandom().connect(provider); - - await expect( - account.k1RemoveOwner(await randomWallet.getAddress()), - ).to.be.revertedWithCustomError( - account, - 'NOT_FROM_SELF_OR_MODULE', - ); - }); - - it('Clear owners with unauthorized msg.sender', async function () { - const newKeyPair = genKey(); - const newPublicKey = encodePublicKey(newKeyPair); - - await expect( - account.resetOwners(newPublicKey), - ).to.be.revertedWithCustomError( - account, - 'NOT_FROM_SELF_OR_MODULE', - ); - }); - - it('Reset owners new r1 owner invalid length', async function () { - let invalidLength = Math.ceil(Math.random() * 200) * 2; - invalidLength = invalidLength === 128 ? 130 : invalidLength; - - const invalidPubkey = '0x' + 'C'.repeat(invalidLength); - - const resetOwnersTx = - await account.resetOwners.populateTransaction( - invalidPubkey, - ); - - const tx = await prepareTeeTx( - provider, - account, - resetOwnersTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - - // await expect(txReceipt.wait()).to.be.revertedWithCustomError( - // account, - // 'INVALID_PUBKEY_LENGTH', - // ); - - try { - await txReceipt.wait(); - assert(false, 'Should revert'); - } catch (e) {} - }); - }); - }); - - describe('Validator manager', function () { - describe('Should not revert when', function () { - it('Adds a new r1 validator correctly', async function () { - const newR1Validator = await deployContract( - hre, - 'MockValidator', - undefined, - { - wallet: richWallet, - silent: true, - }, - ); - - expect( - await account.r1IsValidator( - await newR1Validator.getAddress(), - ), - ).to.be.false; - - const addValidatorTx = - await account.r1AddValidator.populateTransaction( - await newR1Validator.getAddress(), - ); - - const tx = await prepareTeeTx( - provider, - account, - addValidatorTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - await txReceipt.wait(); - - expect( - await account.r1IsValidator( - await newR1Validator.getAddress(), - ), - ).to.be.true; - - const expectedValidators = [ - await newR1Validator.getAddress(), - await mockValidator.getAddress(), - ]; - - expect(await account.r1ListValidators()).to.deep.eq( - expectedValidators, - ); - }); - - it('Adds a new k1 validator correctly', async function () { - const k1Validator = await deployContract( - hre, - 'EOAValidator', - undefined, - { - wallet: richWallet, - silent: true, - }, - ); - - expect( - await account.k1IsValidator(await k1Validator.getAddress()), - ).to.be.false; - - const addValidatorTx = - await account.k1AddValidator.populateTransaction( - await k1Validator.getAddress(), - ); - - const tx = await prepareTeeTx( - provider, - account, - addValidatorTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - await txReceipt.wait(); - - expect( - await account.k1IsValidator(await k1Validator.getAddress()), - ).to.be.true; - - const expectedValidators = [await k1Validator.getAddress()]; - - expect(await account.k1ListValidators()).to.deep.eq( - expectedValidators, - ); - }); - - it('Removes an r1 validator correctly', async function () { - const newR1Validator = await deployContract( - hre, - 'MockValidator', - undefined, - { - wallet: richWallet, - silent: true, - }, - ); - - const addValidatorTx = - await account.r1AddValidator.populateTransaction( - await newR1Validator.getAddress(), - ); - - const tx = await prepareTeeTx( - provider, - account, - addValidatorTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - await txReceipt.wait(); - - expect( - await account.r1IsValidator( - await newR1Validator.getAddress(), - ), - ).to.be.true; - - const removeValidatorTx = - await account.r1RemoveValidator.populateTransaction( - await newR1Validator.getAddress(), - ); - - const tx2 = await prepareTeeTx( - provider, - account, - removeValidatorTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt2 = await provider.broadcastTransaction( - utils.serializeEip712(tx2), - ); - await txReceipt2.wait(); - - expect( - await account.r1IsValidator( - await newR1Validator.getAddress(), - ), - ).to.be.false; - - const expectedValidators = [await mockValidator.getAddress()]; - - expect(await account.r1ListValidators()).to.deep.eq( - expectedValidators, - ); - }); - - it('Removes a k1 validator correctly', async function () { - const k1Validator = await deployContract( - hre, - 'EOAValidator', - undefined, - { - wallet: richWallet, - silent: true, - }, - ); - - const addValidatorTx = - await account.k1AddValidator.populateTransaction( - await k1Validator.getAddress(), - ); - - const tx = await prepareTeeTx( - provider, - account, - addValidatorTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - await txReceipt.wait(); - - expect( - await account.k1IsValidator(await k1Validator.getAddress()), - ).to.be.true; - - const removeValidatorTx = - await account.k1RemoveValidator.populateTransaction( - await k1Validator.getAddress(), - ); - - const tx2 = await prepareTeeTx( - provider, - account, - removeValidatorTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt2 = await provider.broadcastTransaction( - utils.serializeEip712(tx2), - ); - await txReceipt2.wait(); - - expect( - await account.k1IsValidator(await k1Validator.getAddress()), - ).to.be.false; - - const expectedValidators: Array = []; - - expect(await account.k1ListValidators()).to.deep.eq( - expectedValidators, - ); - }); - }); - - describe('Should revert when', function () { - it('Adds r1 validator with unauthorized msg.sender', async function () { - const newR1Validator = await deployContract( - hre, - 'MockValidator', - undefined, - { - wallet: richWallet, - silent: true, - }, - ); - - await expect( - account.r1AddValidator(await newR1Validator.getAddress()), - ).to.be.revertedWithCustomError( - account, - 'NOT_FROM_SELF_OR_MODULE', - ); - }); - - it('Adds r1 validator with WRONG interface', async function () { - const wrongInterfaceValidator = await deployContract( - hre, - 'EOAValidator', - undefined, - { - wallet: richWallet, - silent: true, - }, - ); - - const addValidatorTx = - await account.r1AddValidator.populateTransaction( - await wrongInterfaceValidator.getAddress(), - ); - - const tx = await prepareTeeTx( - provider, - account, - addValidatorTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - - // await expect(txReceipt.wait()).to.be.revertedWithCustomError( - // account, - // 'VALIDATOR_ERC165_FAIL', - // ); - - try { - await txReceipt.wait(); - assert(false, 'Should revert'); - } catch (e) {} - }); - - it('Adds r1 validator with NO interface', async function () { - const noInterfaceValidator = Wallet.createRandom(); - - const addValidatorTx = - await account.r1AddValidator.populateTransaction( - await noInterfaceValidator.getAddress(), - ); - - const tx = await prepareTeeTx( - provider, - account, - addValidatorTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - - // await expect(txReceipt.wait()).to.be.revertedWithCustomError( - // account, - // 'VALIDATOR_ERC165_FAIL', - // ); - - try { - await txReceipt.wait(); - assert(false, 'Should revert'); - } catch (e) {} - }); - - it('Removes r1 validator with unauthorized msg.sender', async function () { - const newR1Validator = await deployContract( - hre, - 'MockValidator', - undefined, - { - wallet: richWallet, - silent: true, - }, - ); - - const addValidatorTx = - await account.r1AddValidator.populateTransaction( - await newR1Validator.getAddress(), - ); - - const tx = await prepareTeeTx( - provider, - account, - addValidatorTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - await txReceipt.wait(); - - expect( - await account.r1IsValidator( - await newR1Validator.getAddress(), - ), - ).to.be.true; - - await expect( - account.r1RemoveValidator( - await newR1Validator.getAddress(), - ), - ).to.be.revertedWithCustomError( - account, - 'NOT_FROM_SELF_OR_MODULE', - ); - }); - - it('Removes last r1 validator', async function () { - const removeValidatorTx = - await account.r1RemoveValidator.populateTransaction( - await mockValidator.getAddress(), - ); - - const tx = await prepareTeeTx( - provider, - account, - removeValidatorTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - - // await expect(txReceipt.wait()).to.be.revertedWithCustomError( - // account, - // 'EMPTY_R1_VALIDATORS', - // ); - - try { - await txReceipt.wait(); - assert(false, 'Should revert'); - } catch (e) {} - }); - - it('Adds k1 validator with unauthorized msg.sender', async function () { - const k1Validator = await deployContract( - hre, - 'EOAValidator', - undefined, - { - wallet: richWallet, - silent: true, - }, - ); - - await expect( - account.k1AddValidator(await k1Validator.getAddress()), - ).to.be.revertedWithCustomError( - account, - 'NOT_FROM_SELF_OR_MODULE', - ); - }); - - it('Adds k1 validator with WRONG interface', async function () { - const wrongInterfaceValidator = await deployContract( - hre, - 'MockValidator', - undefined, - { - wallet: richWallet, - silent: true, - }, - ); - - const addValidatorTx = - await account.k1AddValidator.populateTransaction( - await wrongInterfaceValidator.getAddress(), - ); - - const tx = await prepareTeeTx( - provider, - account, - addValidatorTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - - // await expect(txReceipt.wait()).to.be.revertedWithCustomError( - // account, - // 'VALIDATOR_ERC165_FAIL', - // ); - - try { - await txReceipt.wait(); - assert(false, 'Should revert'); - } catch (e) {} - }); - - it('Adds k1 validator with NO interface', async function () { - const noInterfaceValidator = Wallet.createRandom(); - - const addValidatorTx = - await account.k1AddValidator.populateTransaction( - await noInterfaceValidator.getAddress(), - ); - - const tx = await prepareTeeTx( - provider, - account, - addValidatorTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - - // await expect(txReceipt.wait()).to.be.revertedWithCustomError( - // account, - // 'VALIDATOR_ERC165_FAIL', - // ); - - try { - await txReceipt.wait(); - assert(false, 'Should revert'); - } catch (e) {} - }); - - it('Removes k1 validator with unauthorized msg.sender', async function () { - const k1Validator = await deployContract( - hre, - 'EOAValidator', - undefined, - { - wallet: richWallet, - silent: true, - }, - ); - - const addValidatorTx = - await account.k1AddValidator.populateTransaction( - await k1Validator.getAddress(), - ); - - const tx = await prepareTeeTx( - provider, - account, - addValidatorTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - await txReceipt.wait(); - - expect( - await account.k1IsValidator(await k1Validator.getAddress()), - ).to.be.true; - - await expect( - account.k1RemoveValidator(await k1Validator.getAddress()), - ).to.be.revertedWithCustomError( - account, - 'NOT_FROM_SELF_OR_MODULE', - ); - }); - }); - }); - - describe('Module manager', function () { - describe('Should not revert when', function () { - it('Adds a new module correctly', async function () { - const mockModule = await deployContract( - hre, - 'MockModule', - undefined, - { - wallet: richWallet, - silent: true, - }, - ); - - expect(await account.isModule(await mockModule.getAddress())).to - .be.false; - - const initData = AbiCoder.defaultAbiCoder().encode( - ['uint256'], - [parseEther('42')], - ); - const moduleAndData = concat([ - await mockModule.getAddress(), - initData, - ]); - - const addModuleTx = await account.addModule.populateTransaction( - moduleAndData, - ); - - const tx = await prepareTeeTx( - provider, - account, - addModuleTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - await txReceipt.wait(); - - expect(await account.isModule(await mockModule.getAddress())).to - .be.true; - - const expectedModules = [await mockModule.getAddress()]; - - expect(await account.listModules()).to.deep.eq(expectedModules); - }); - - it('Removes a module correctly', async function () { - const mockModule = await deployContract( - hre, - 'MockModule', - undefined, - { - wallet: richWallet, - silent: true, - }, - ); - - const initData = AbiCoder.defaultAbiCoder().encode( - ['uint256'], - [parseEther('42')], - ); - const moduleAndData = concat([ - await mockModule.getAddress(), - initData, - ]); - - const addModuleTx = await account.addModule.populateTransaction( - moduleAndData, - ); - - const tx = await prepareTeeTx( - provider, - account, - addModuleTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - await txReceipt.wait(); - - expect(await account.isModule(await mockModule.getAddress())).to - .be.true; - - const removeModuleTx = - await account.removeModule.populateTransaction( - await mockModule.getAddress(), - ); - - const tx2 = await prepareTeeTx( - provider, - account, - removeModuleTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt2 = await provider.broadcastTransaction( - utils.serializeEip712(tx2), - ); - await txReceipt2.wait(); - - expect(await account.isModule(await mockModule.getAddress())).to - .be.false; - - const expectedModules: Array = []; - - expect(await account.listModules()).to.deep.eq(expectedModules); - }); - - it('Executes from module correctly', async function () { - const amount = parseEther('42'); - const delta = parseEther('0.01'); - - const mockModule = await deployContract( - hre, - 'MockModule', - undefined, - { - wallet: richWallet, - silent: true, - }, - ); - - const initData = AbiCoder.defaultAbiCoder().encode( - ['uint256'], - [parseEther('42')], - ); - const moduleAndData = concat([ - await mockModule.getAddress(), - initData, - ]); - - const addModuleTx = await account.addModule.populateTransaction( - moduleAndData, - ); - - const tx = await prepareTeeTx( - provider, - account, - addModuleTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - await txReceipt.wait(); - - const accountBalanceBefore = await provider.getBalance( - await account.getAddress(), - ); - const receiverBalanceBefore = await provider.getBalance( - await richWallet.getAddress(), - ); - - await mockModule.testExecuteFromModule( - await account.getAddress(), - await richWallet.getAddress(), - ); - - const accountBalanceAfter = await provider.getBalance( - await account.getAddress(), - ); - const receiverBalanceAfter = await provider.getBalance( - await richWallet.getAddress(), - ); - - expect(accountBalanceAfter).to.be.closeTo( - accountBalanceBefore - amount, - delta, - ); - - expect(receiverBalanceAfter).to.be.closeTo( - receiverBalanceBefore + amount, - delta, - ); - }); - }); - describe('Should revert when', function () { - it('Adds module with unauthorized msg.sender', async function () { - const mockModule = await deployContract( - hre, - 'MockModule', - undefined, - { - wallet: richWallet, - silent: true, - }, - ); - - const initData = AbiCoder.defaultAbiCoder().encode( - ['uint256'], - [parseEther('42')], - ); - const moduleAndData = concat([ - await mockModule.getAddress(), - initData, - ]); - - await expect( - account.addModule(moduleAndData), - ).to.be.revertedWithCustomError( - account, - 'NOT_FROM_SELF_OR_MODULE', - ); - }); - - it('Adds module with invalid moduleAndData length', async function () { - const mockModule = await deployContract( - hre, - 'MockModule', - undefined, - { - wallet: richWallet, - silent: true, - }, - ); - - const moduleAndData = (await mockModule.getAddress()).slice( - 0, - 10, - ); - - const addModuleTx = await account.addModule.populateTransaction( - moduleAndData, - ); - - const tx = await prepareTeeTx( - provider, - account, - addModuleTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - - // await expect(txReceipt.wait()).to.be.revertedWithCustomError( - // account, - // 'EMPTY_MODULE_ADDRESS', - // ); - - try { - await txReceipt.wait(); - assert(false, 'Should revert'); - } catch (e) {} - }); - - it('Adds module with no interface', async function () { - const noInterfaceModule = Wallet.createRandom(); - - const initData = AbiCoder.defaultAbiCoder().encode( - ['uint256'], - [parseEther('42')], - ); - const moduleAndData = concat([ - await noInterfaceModule.getAddress(), - initData, - ]); - - const addModuleTx = await account.addModule.populateTransaction( - moduleAndData, - ); - - const tx = await prepareTeeTx( - provider, - account, - addModuleTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - - // await expect(txReceipt.wait()).to.be.revertedWithCustomError( - // account, - // 'MODULE_ERC165_FAIL', - // ); - - try { - await txReceipt.wait(); - assert(false, 'Should revert'); - } catch (e) {} - }); - - it('Removes module with unauthorized msg.sender', async function () { - const mockModule = await deployContract( - hre, - 'MockModule', - undefined, - { - wallet: richWallet, - silent: true, - }, - ); - - const initData = AbiCoder.defaultAbiCoder().encode( - ['uint256'], - [parseEther('42')], - ); - const moduleAndData = concat([ - await mockModule.getAddress(), - initData, - ]); - - const addModuleTx = await account.addModule.populateTransaction( - moduleAndData, - ); - - const tx = await prepareTeeTx( - provider, - account, - addModuleTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - await txReceipt.wait(); - - await expect( - account.removeModule(await mockModule.getAddress()), - ).to.be.revertedWithCustomError( - account, - 'NOT_FROM_SELF_OR_MODULE', - ); - }); - }); - }); - - describe('Upgrade manager', function () { - describe('Should not revert when', function () { - it('Upgrades to a new implementation correctly', async function () { - const mockImplementation = await deployContract( - hre, - 'MockImplementation', - undefined, - { - wallet: richWallet, - silent: true, - }, - ); - - const upgradeTx = await account.upgradeTo.populateTransaction( - await mockImplementation.getAddress(), - ); - - const tx = await prepareTeeTx( - provider, - account, - upgradeTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - - await txReceipt.wait(); - - expect(await account.implementation()).to.eq( - await mockImplementation.getAddress(), - ); - }); - }); - describe('Should revert when', function () { - it('Upgrades to a new implementation with unauthorized msg.sender', async function () { - const mockImplementation = await deployContract( - hre, - 'MockImplementation', - undefined, - { - wallet: richWallet, - silent: true, - }, - ); - - await expect( - account.upgradeTo(await mockImplementation.getAddress()), - ).to.be.revertedWithCustomError(account, 'NOT_FROM_SELF'); - }); - - it('Upgrades to same implementation', async function () { - const upgradeTx = await account.upgradeTo.populateTransaction( - await implementation.getAddress(), - ); - - const tx = await prepareTeeTx( - provider, - account, - upgradeTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - - // await expect(txReceipt.wait()).to.be.revertedWithCustomError( - // account, - // 'SAME_IMPLEMENTATION', - // ); - - try { - await txReceipt.wait(); - assert(false, 'Should revert'); - } catch (e) {} - }); - }); - }); - - describe('Hook manager', function () { - describe('Should not revert when', function () { - it('Adds a new validation hook correctly', async function () { - const mockHook = await deployContract( - hre, - 'MockValidationHook', - undefined, - { - wallet: richWallet, - silent: true, - }, - ); - - expect(await account.isHook(await mockHook.getAddress())).to.be - .false; - - const addHookTx = await account.addHook.populateTransaction( - await mockHook.getAddress(), - true, - ); - - const tx = await prepareTeeTx( - provider, - account, - addHookTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - await txReceipt.wait(); - - expect(await account.isHook(await mockHook.getAddress())).to.be - .true; - - const expectedHooks = [await mockHook.getAddress()]; - expect(await account.listHooks(true)).to.deep.eq(expectedHooks); - }); - - it('Adds a new execution hook correctly', async function () { - const mockHook = await deployContract( - hre, - 'MockExecutionHook', - undefined, - { - wallet: richWallet, - silent: true, - }, - ); - - expect(await account.isHook(await mockHook.getAddress())).to.be - .false; - - const addHookTx = await account.addHook.populateTransaction( - await mockHook.getAddress(), - false, - ); - - const tx = await prepareTeeTx( - provider, - account, - addHookTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - await txReceipt.wait(); - - expect(await account.isHook(await mockHook.getAddress())).to.be - .true; - - const expectedHooks = [await mockHook.getAddress()]; - expect(await account.listHooks(false)).to.deep.eq( - expectedHooks, - ); - }); - - it('Removes a hook correctly', async function () { - const mockHook = await deployContract( - hre, - 'MockExecutionHook', - undefined, - { - wallet: richWallet, - silent: true, - }, - ); - - expect(await account.isHook(await mockHook.getAddress())).to.be - .false; - - const addHookTx = await account.addHook.populateTransaction( - await mockHook.getAddress(), - false, - ); - - const tx = await prepareTeeTx( - provider, - account, - addHookTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - await txReceipt.wait(); - - expect(await account.isHook(await mockHook.getAddress())).to.be - .true; - - const removeHookTx = - await account.removeHook.populateTransaction( - await mockHook.getAddress(), - false, - ); - - const tx2 = await prepareTeeTx( - provider, - account, - removeHookTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt2 = await provider.broadcastTransaction( - utils.serializeEip712(tx2), - ); - txReceipt2.wait(); - - expect(await account.isHook(await mockHook.getAddress())).to.be - .false; - - const expectedHooks: Array = []; - expect(await account.listHooks(true)).to.deep.eq(expectedHooks); - }); - - it('Sets hook data correctly', async function () { - const mockHook = await deployContract( - hre, - 'MockValidationHook', - undefined, - { - wallet: richWallet, - silent: true, - }, - ); - - expect(await account.isHook(await mockHook.getAddress())).to.be - .false; - - const addHookTx = await account.addHook.populateTransaction( - await mockHook.getAddress(), - true, - ); - - const tx = await prepareTeeTx( - provider, - account, - addHookTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - await txReceipt.wait(); - - expect(await account.isHook(await mockHook.getAddress())).to.be - .true; - - const key = randomBytes(32); - const data = '0xc1ae'; - - await mockHook.setHookData( - await account.getAddress(), - key, - data, - ); - - expect( - await account.getHookData(await mockHook.getAddress(), key), - ).to.eq(data); - }); - - it('Runs validation hooks successfully', async function () { - const mockHook = await deployContract( - hre, - 'MockValidationHook', - undefined, - { - wallet: richWallet, - silent: true, - }, - ); - - expect(await account.isHook(await mockHook.getAddress())).to.be - .false; - - const addHookTx = await account.addHook.populateTransaction( - await mockHook.getAddress(), - true, - ); - - const tx = await prepareTeeTx( - provider, - account, - addHookTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - await txReceipt.wait(); - - const transfer = ethTransfer(await richWallet.getAddress(), 1); - - const hookData = [ - AbiCoder.defaultAbiCoder().encode(['bool'], [false]), - ]; - - const tx2 = await prepareTeeTx( - provider, - account, - transfer, - await mockValidator.getAddress(), - keyPair, - hookData, - ); - - const txReceipt2 = await provider.broadcastTransaction( - utils.serializeEip712(tx2), - ); - await txReceipt2.wait(); - }); - }); - - describe('Should revert when', function () { - it('Adds hook with unauthorized msg.sender', async function () { - const mockHook = await deployContract( - hre, - 'MockValidationHook', - undefined, - { - wallet: richWallet, - silent: true, - }, - ); - - await expect( - account.addHook(await mockHook.getAddress(), true), - ).to.be.revertedWithCustomError( - account, - 'NOT_FROM_SELF_OR_MODULE', - ); - }); - - it('Adds hook with invalid hookAndData length', async function () { - const mockHook = await deployContract( - hre, - 'MockValidationHook', - undefined, - { - wallet: richWallet, - silent: true, - }, - ); - - const hookAndData = (await mockHook.getAddress()).slice(0, 10); - - const addHookTx = await account.addHook.populateTransaction( - hookAndData, - true, - ); - - const tx = await prepareTeeTx( - provider, - account, - addHookTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - - // await expect(txReceipt.wait()).to.be.revertedWithCustomError( - // account, - // 'EMPTY_HOOK_ADDRESS', - // ); - - try { - await txReceipt.wait(); - assert(false, 'Should revert'); - } catch (e) {} - }); - - it('Adds hook with NO interface', async function () { - const noInterfaceHook = Wallet.createRandom(); - - const addHookTx = await account.addHook.populateTransaction( - await noInterfaceHook.getAddress(), - true, - ); - - const tx = await prepareTeeTx( - provider, - account, - addHookTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - - // await expect(txReceipt.wait()).to.be.revertedWithCustomError( - // account, - // 'HOOK_ERC165_FAIL', - // ); - - try { - await txReceipt.wait(); - assert(false, 'Should revert'); - } catch (e) {} - }); - - it('Removes hook with unauthorized msg.sender', async function () { - const mockHook = await deployContract( - hre, - 'MockValidationHook', - undefined, - { - wallet: richWallet, - silent: true, - }, - ); - - const addHookTx = await account.addHook.populateTransaction( - await mockHook.getAddress(), - true, - ); - - const tx = await prepareTeeTx( - provider, - account, - addHookTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - await txReceipt.wait(); - - await expect( - account.removeHook(await mockHook.getAddress(), true), - ).to.be.revertedWithCustomError( - account, - 'NOT_FROM_SELF_OR_MODULE', - ); - }); - - it('Sets hook data with unauthorized msg.sender', async function () { - const key = randomBytes(32); - const data = '0xc1ae'; - - await expect( - account.setHookData(key, data), - ).to.be.revertedWithCustomError(account, 'NOT_FROM_HOOK'); - }); - - it('Sets hook data with invalid key', async function () { - const mockHook = await deployContract( - hre, - 'MockValidationHook', - undefined, - { - wallet: richWallet, - silent: true, - }, - ); - - const addHookTx = await account.addHook.populateTransaction( - await mockHook.getAddress(), - true, - ); - - const tx = await prepareTeeTx( - provider, - account, - addHookTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - await txReceipt.wait(); - - const key = solidityPackedKeccak256( - ['string'], - ['HookManager.context'], - ); - const data = '0xc1ae'; - - await expect( - mockHook.setHookData(await account.getAddress(), key, data), - ).to.be.revertedWithCustomError(account, 'INVALID_KEY'); - }); - - it('Run validation hooks fails', async function () { - const mockHook = await deployContract( - hre, - 'MockValidationHook', - undefined, - { - wallet: richWallet, - silent: true, - }, - ); - - const addHookTx = await account.addHook.populateTransaction( - await mockHook.getAddress(), - true, - ); - - const tx = await prepareTeeTx( - provider, - account, - addHookTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - await txReceipt.wait(); - - const transfer = ethTransfer(await richWallet.getAddress(), 5); - - const hookData = [ - AbiCoder.defaultAbiCoder().encode(['bool'], [true]), - ]; - - const tx2 = await prepareTeeTx( - provider, - account, - transfer, - await mockValidator.getAddress(), - keyPair, - hookData, - ); - - try { - await provider.broadcastTransaction( - utils.serializeEip712(tx2), - ); - assert(false, 'Should revert'); - } catch (e) {} - }); - - it('Run execution hooks fails', async function () { - const mockHook = await deployContract( - hre, - 'MockExecutionHook', - undefined, - { - wallet: richWallet, - silent: true, - }, - ); - - const addHookTx = await account.addHook.populateTransaction( - await mockHook.getAddress(), - false, - ); - - const tx = await prepareTeeTx( - provider, - account, - addHookTx, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - await txReceipt.wait(); - - const transfer = ethTransfer(await richWallet.getAddress(), 5); - - const tx2 = await prepareTeeTx( - provider, - account, - transfer, - await mockValidator.getAddress(), - keyPair, - ); - - const txReceipt2 = await provider.broadcastTransaction( - utils.serializeEip712(tx2), - ); - - try { - await txReceipt2.wait(); - assert(false, 'Should revert'); - } catch (e) {} - }); - }); - }); - - describe('Paymaster', function () { - let mockToken: Contract; - let gaslessPaymaster: Contract; - let erc20Paymaster: Contract; - let subsidizerPaymaster: Contract; - - beforeEach(async function () { - mockToken = await deployContract(hre, 'MockStable', undefined, { - wallet: richWallet, - silent: true, - }); - - gaslessPaymaster = await deployContract( - hre, - 'GaslessPaymaster', - [await registry.getAddress(), 2], - { - wallet: richWallet, - silent: true, - }, - ); - - erc20Paymaster = await deployContract( - hre, - 'ERC20PaymasterMock', - [ - [ - { - tokenAddress: await mockToken.getAddress(), - decimals: 18, - priceMarkup: 20000, - }, - ], - ], - { - wallet: richWallet, - silent: true, - }, - ); - - subsidizerPaymaster = await deployContract( - hre, - 'SubsidizerPaymasterMock', - [ - [ - { - tokenAddress: await mockToken.getAddress(), - decimals: 18, - priceMarkup: 20000, - }, - ], - await registry.getAddress(), - ], - { - wallet: richWallet, - silent: true, - }, - ); - - await mockToken.mint(await account.getAddress(), parseEther('100')); - - await ( - await richWallet.sendTransaction({ - to: await gaslessPaymaster.getAddress(), - value: parseEther('50'), - }) - ).wait(); - - await ( - await richWallet.sendTransaction({ - to: await erc20Paymaster.getAddress(), - value: parseEther('50'), - }) - ).wait(); - - await ( - await richWallet.sendTransaction({ - to: await subsidizerPaymaster.getAddress(), - value: parseEther('50'), - }) - ).wait(); - }); - - it('Should fund the account with mock token', async function () { - expect( - await mockToken.balanceOf(await account.getAddress()), - ).to.be.eq(parseEther('100')); - }); - - it('Should fund the paymasters', async function () { - expect( - await provider.getBalance(await gaslessPaymaster.getAddress()), - ).to.eq(parseEther('50')); - expect( - await provider.getBalance(await erc20Paymaster.getAddress()), - ).to.eq(parseEther('50')); - expect( - await provider.getBalance( - await subsidizerPaymaster.getAddress(), - ), - ).to.eq(parseEther('50')); - }); - - it('Should prepare an oracle payload', async function () { - //TODO - }); - - it('Should pay gas with token', async function () { - //TODO - }); - - it('Should send tx without paying gas', async function () { - const amount = parseEther('10'); - - const accountBalanceBefore = await provider.getBalance( - await account.getAddress(), - ); - const receiverBalanceBefore = await provider.getBalance( - await richWallet.getAddress(), - ); - const paymasterBalanceBefore = await provider.getBalance( - await gaslessPaymaster.getAddress(), - ); - - const transfer = ethTransfer(await richWallet.getAddress(), amount); - const tx = await prepareTeeTx( - provider, - account, - transfer, - await mockValidator.getAddress(), - keyPair, - [], - getGaslessPaymasterInput(await gaslessPaymaster.getAddress()), - ); - - const txReceipt = await provider.broadcastTransaction( - utils.serializeEip712(tx), - ); - - await txReceipt.wait(); - - const accountBalanceAfter = await provider.getBalance( - await account.getAddress(), - ); - const receiverBalanceAfter = await provider.getBalance( - await richWallet.getAddress(), - ); - const paymasterBalanceAfter = await provider.getBalance( - await gaslessPaymaster.getAddress(), - ); - - expect(accountBalanceAfter + amount).to.be.equal( - accountBalanceBefore, - ); - expect(receiverBalanceBefore + amount).to.be.equal( - receiverBalanceAfter, - ); - expect(paymasterBalanceAfter).is.lessThan(paymasterBalanceBefore); - }); - - it('Should pay subsidized gas with token', async function () { - //TODO - }); - - describe('Subsidizer refunds calculations', function () { - const MAX_GAS_TO_SUBSIDIZE = 1_250_000n; - - const calcRefund = ( - gaslimit: bigint, - gasused: bigint, - max: bigint, - ): bigint => { - const userpaid = gaslimit > max ? gaslimit - max : 0; - const gasrefunded = gaslimit - gasused; - - if (userpaid === 0) { - return 0n; - } - - if (max > gasused) { - return userpaid; - } else { - return gasrefunded; - } - }; - - const calcExpected = ( - refunded: bigint, - maxFeePerGas: bigint, - rate: bigint, - ): bigint => { - return (refunded * maxFeePerGas * rate) / WeiPerEther; - }; - - type Config = { - gasLimit: bigint; - gasUsed: bigint; - maxFeePerGas: bigint; - rate: bigint; - maxGasToSubsidize: bigint; - }; - - const checkRefund = async ( - pmConfig: Config, - ): Promise<[bigint, bigint]> => { - const expected = calcExpected( - calcRefund( - pmConfig.gasLimit, - pmConfig.gasUsed, - pmConfig.maxGasToSubsidize, - ), - pmConfig.maxFeePerGas, - pmConfig.rate, - ); - - const real = await subsidizerPaymaster.calcRefundAmount( - pmConfig.gasLimit, - pmConfig.gasLimit - pmConfig.gasUsed, - pmConfig.maxFeePerGas, - pmConfig.rate, - ); - - return [expected, real]; - }; - - it('Should calculate if gasUsed is bigger than maxGasToSubsidize', async function () { - const pmRate = await subsidizerPaymaster.getPairPrice( - await mockToken.getAddress(), - '0x', - ); - - const pmConfig: Config = { - gasLimit: 2_000_000n, - gasUsed: 1_500_000n, - maxFeePerGas: 100n, - rate: BigInt(pmRate.toString()), - maxGasToSubsidize: 1_000_000n, - }; - - const values = await checkRefund(pmConfig); - expect(values[0]).to.be.equal(values[1]); - }); - - it('Should calculate if gasUsed is equal to gasLimit and bigger than maxGasToSubsidize', async function () { - const pmRate = await subsidizerPaymaster.getPairPrice( - await mockToken.getAddress(), - '0x', - ); - - const pmConfig = { - gasLimit: 2_000_000n, - gasUsed: 2_000_000n, - maxFeePerGas: 100n, - rate: BigInt(pmRate.toString()), - maxGasToSubsidize: MAX_GAS_TO_SUBSIDIZE, - }; - - const values = await checkRefund(pmConfig); - expect(values[0]).to.be.equal(values[1]); - }); - - it('Should calculate if gasUsed is less than gasLimit and both less than maxGasToSubsidize', async function () { - const pmRate = await subsidizerPaymaster.getPairPrice( - await mockToken.getAddress(), - '0x', - ); - - const pmConfig = { - gasLimit: 1_000_000n, - gasUsed: 500_000n, - maxFeePerGas: 100n, - rate: BigInt(pmRate.toString()), - maxGasToSubsidize: MAX_GAS_TO_SUBSIDIZE, - }; - - const values = await checkRefund(pmConfig); - expect(values[0]).to.be.equal(values[1]); - }); - - it('Should calculate if gasUsed is equal to gasLimit and both less than maxGasToSubsidize', async function () { - const pmRate = await subsidizerPaymaster.getPairPrice( - await mockToken.getAddress(), - '0x', - ); - - const pmConfig = { - gasLimit: 500_000n, - gasUsed: 500_000n, - maxFeePerGas: 100n, - rate: BigInt(pmRate.toString()), - maxGasToSubsidize: MAX_GAS_TO_SUBSIDIZE, - }; - - const values = await checkRefund(pmConfig); - expect(values[0]).to.be.equal(values[1]); - }); - - it('Should calculate if gasUsed is less then maxGasToSubsidize ', async function () { - const pmRate = await subsidizerPaymaster.getPairPrice( - await mockToken.getAddress(), - '0x', - ); - - const pmConfig = { - gasLimit: 2_000_000n, - gasUsed: 500_000n, - maxFeePerGas: 100n, - rate: BigInt(pmRate.toString()), - maxGasToSubsidize: MAX_GAS_TO_SUBSIDIZE, - }; - - const values = await checkRefund(pmConfig); - expect(values[0]).to.be.equal(values[1]); - }); - }); - }); -}); diff --git a/test/utils/p256.ts b/test/utils/p256.ts deleted file mode 100644 index 4e187f7..0000000 --- a/test/utils/p256.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright Clave - All Rights Reserved - * Unauthorized copying of this file, via any medium is strictly prohibited - * Proprietary and confidential - */ -import elliptic from 'elliptic'; - -export function genKey(): elliptic.ec.KeyPair { - const ec = new elliptic.ec('p256'); - return ec.genKeyPair(); -} - -export function encodePublicKey(key: elliptic.ec.KeyPair): string { - const pubKey = key.getPublic(); - const x = pubKey.getX().toString('hex').padStart(64, '0'); - const y = pubKey.getY().toString('hex').padStart(64, '0'); - - return '0x' + x + y; -} - -export function sign(msg: string, key: elliptic.ec.KeyPair): string { - const buffer = Buffer.from(msg.slice(2), 'hex'); - const signature = key.sign(buffer); - const r = signature.r.toString('hex').padStart(64, '0'); - const s = signature.s.toString('hex').padStart(64, '0'); - return '0x' + r + s; -} diff --git a/test/utils/paymaster.ts b/test/utils/paymaster.ts deleted file mode 100644 index 9912045..0000000 --- a/test/utils/paymaster.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright Clave - All Rights Reserved - * Unauthorized copying of this file, via any medium is strictly prohibited - * Proprietary and confidential - */ -import type { ethers } from 'ethers'; -import { type types, utils } from 'zksync-ethers'; - -export function getGaslessPaymasterInput( - paymasterAddress: types.Address, -): types.PaymasterParams { - return utils.getPaymasterParams(paymasterAddress, { - type: 'General', - innerInput: new Uint8Array(), - }); -} - -export function getERC20PaymasterInput( - paymasterAddress: types.Address, - tokenAddress: types.Address, - minimalAllowance: bigint, - oraclePayload: ethers.BytesLike, -): types.PaymasterParams { - return utils.getPaymasterParams(paymasterAddress, { - type: 'ApprovalBased', - token: tokenAddress, - minimalAllowance, - innerInput: oraclePayload, - }); -} diff --git a/test/utils/transaction.ts b/test/utils/transaction.ts deleted file mode 100644 index 36a9e8d..0000000 --- a/test/utils/transaction.ts +++ /dev/null @@ -1,155 +0,0 @@ -/** - * Copyright Clave - All Rights Reserved - * Unauthorized copying of this file, via any medium is strictly prohibited - * Proprietary and confidential - */ -import type { ec } from 'elliptic'; -import { type BigNumberish, ethers, parseEther } from 'ethers'; -import type { Contract, Provider, types } from 'zksync-ethers'; -import { EIP712Signer, utils } from 'zksync-ethers'; - -import type { ClaveProxy } from '../../typechain-types'; -import type { CallStruct } from '../../typechain-types/contracts/batch/BatchCaller'; -import { sign } from './p256'; - -export const ethTransfer = ( - to: string, - value: BigNumberish, -): types.TransactionLike => { - return { - to, - value, - data: '0x', - }; -}; - -export async function prepareMockTx( - provider: Provider, - account: ClaveProxy, - tx: types.TransactionLike, - validatorAddress: string, - paymasterParams?: types.PaymasterParams, -): Promise { - const abiCoder = ethers.AbiCoder.defaultAbiCoder(); - const signature = abiCoder.encode( - ['bytes', 'address', 'bytes[]'], - ['0x' + 'C1AE'.repeat(32), validatorAddress, []], - ); - - tx = { - ...tx, - from: await account.getAddress(), - nonce: await provider.getTransactionCount(await account.getAddress()), - gasLimit: 10_000_000, - gasPrice: await provider.getGasPrice(), - chainId: (await provider.getNetwork()).chainId, - type: 113, - customData: { - gasPerPubdata: utils.DEFAULT_GAS_PER_PUBDATA_LIMIT, - customSignature: signature, - paymasterParams: paymasterParams, - } as types.Eip712Meta, - }; - - return tx; -} - -export async function prepareTeeTx( - provider: Provider, - account: Contract, - tx: types.TransactionLike, - validatorAddress: string, - keyPair: ec.KeyPair, - hookData: Array = [], - paymasterParams?: types.PaymasterParams, -): Promise { - if (tx.value == undefined) { - tx.value = parseEther('0'); - } - - tx = { - ...tx, - from: await account.getAddress(), - nonce: await provider.getTransactionCount(await account.getAddress()), - gasLimit: 30_000_000, - gasPrice: await provider.getGasPrice(), - chainId: (await provider.getNetwork()).chainId, - type: 113, - customData: { - gasPerPubdata: utils.DEFAULT_GAS_PER_PUBDATA_LIMIT, - paymasterParams: paymasterParams, - } as types.Eip712Meta, - }; - - const signedTxHash = EIP712Signer.getSignedDigest(tx); - - const abiCoder = ethers.AbiCoder.defaultAbiCoder(); - let signature = sign(signedTxHash.toString(), keyPair); - - signature = abiCoder.encode( - ['bytes', 'address', 'bytes[]'], - [signature, validatorAddress, hookData], - ); - - tx.customData = { - ...tx.customData, - customSignature: signature, - }; - - return tx; -} - -export async function prepareBatchTx( - provider: Provider, - account: Contract, - BatchCallerAddress: string, - calls: Array, - validatorAddress: string, - keyPair: ec.KeyPair, - hookData: Array = [], - paymasterParams?: types.PaymasterParams, -): Promise { - const abiCoder = ethers.AbiCoder.defaultAbiCoder(); - const data = - '0x8f0273a9' + - abiCoder - .encode( - [ - 'tuple(address target, bool allowFailure, uint256 value, bytes callData)[]', - ], - [calls], - ) - .slice(2); - - const tx = { - to: BatchCallerAddress, - from: await account.getAddress(), - nonce: await provider.getTransactionCount(await account.getAddress()), - gasLimit: 30_000_000, - gasPrice: await provider.getGasPrice(), - data, - value: parseEther('0'), - chainId: (await provider.getNetwork()).chainId, - type: 113, - customData: { - gasPerPubdata: utils.DEFAULT_GAS_PER_PUBDATA_LIMIT, - paymasterParams, - } as types.Eip712Meta, - }; - - const signedTxHash = EIP712Signer.getSignedDigest(tx); - - let signature = sign(signedTxHash.toString(), keyPair); - - signature = abiCoder.encode( - ['bytes', 'address', 'bytes[]'], - [signature, validatorAddress, hookData], - ); - - tx.customData = { - ...tx.customData, - customSignature: signature, - }; - - return tx; -} From 92500c457e4c00541f0c606c585dbd1aaa2e6ba5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Sun, 15 Sep 2024 19:30:28 +0300 Subject: [PATCH 068/103] [clave-contracts] chore: rename new tests --- {new-test => test}/accounts/managers/hookmanager.test.ts | 0 {new-test => test}/accounts/managers/modulemanager.test.ts | 0 {new-test => test}/accounts/managers/ownermanager.test.ts | 0 {new-test => test}/accounts/managers/upgrademanager.test.ts | 0 {new-test => test}/accounts/managers/validatormanager.test.ts | 0 {new-test => test}/accounts/transactions.test.ts | 0 {new-test => test}/deployments/deployer.test.ts | 0 {new-test => test}/deployments/rip7212.test.ts | 0 {new-test => test}/paymasters/erc20paymaster.test.ts | 0 {new-test => test}/paymasters/gaslesspaymaster.test.ts | 0 {new-test => test}/utils/deployer.ts | 0 {new-test => test}/utils/fixture.ts | 0 {new-test => test}/utils/managers/hookmanager.ts | 0 {new-test => test}/utils/managers/modulemanager.ts | 0 {new-test => test}/utils/managers/ownermanager.ts | 0 {new-test => test}/utils/managers/upgrademanager.ts | 0 {new-test => test}/utils/managers/validatormanager.ts | 0 {new-test => test}/utils/names.ts | 0 {new-test => test}/utils/p256.ts | 0 {new-test => test}/utils/paymasters.ts | 0 {new-test => test}/utils/snapshot.ts | 0 {new-test => test}/utils/transactions.ts | 0 22 files changed, 0 insertions(+), 0 deletions(-) rename {new-test => test}/accounts/managers/hookmanager.test.ts (100%) rename {new-test => test}/accounts/managers/modulemanager.test.ts (100%) rename {new-test => test}/accounts/managers/ownermanager.test.ts (100%) rename {new-test => test}/accounts/managers/upgrademanager.test.ts (100%) rename {new-test => test}/accounts/managers/validatormanager.test.ts (100%) rename {new-test => test}/accounts/transactions.test.ts (100%) rename {new-test => test}/deployments/deployer.test.ts (100%) rename {new-test => test}/deployments/rip7212.test.ts (100%) rename {new-test => test}/paymasters/erc20paymaster.test.ts (100%) rename {new-test => test}/paymasters/gaslesspaymaster.test.ts (100%) rename {new-test => test}/utils/deployer.ts (100%) rename {new-test => test}/utils/fixture.ts (100%) rename {new-test => test}/utils/managers/hookmanager.ts (100%) rename {new-test => test}/utils/managers/modulemanager.ts (100%) rename {new-test => test}/utils/managers/ownermanager.ts (100%) rename {new-test => test}/utils/managers/upgrademanager.ts (100%) rename {new-test => test}/utils/managers/validatormanager.ts (100%) rename {new-test => test}/utils/names.ts (100%) rename {new-test => test}/utils/p256.ts (100%) rename {new-test => test}/utils/paymasters.ts (100%) rename {new-test => test}/utils/snapshot.ts (100%) rename {new-test => test}/utils/transactions.ts (100%) diff --git a/new-test/accounts/managers/hookmanager.test.ts b/test/accounts/managers/hookmanager.test.ts similarity index 100% rename from new-test/accounts/managers/hookmanager.test.ts rename to test/accounts/managers/hookmanager.test.ts diff --git a/new-test/accounts/managers/modulemanager.test.ts b/test/accounts/managers/modulemanager.test.ts similarity index 100% rename from new-test/accounts/managers/modulemanager.test.ts rename to test/accounts/managers/modulemanager.test.ts diff --git a/new-test/accounts/managers/ownermanager.test.ts b/test/accounts/managers/ownermanager.test.ts similarity index 100% rename from new-test/accounts/managers/ownermanager.test.ts rename to test/accounts/managers/ownermanager.test.ts diff --git a/new-test/accounts/managers/upgrademanager.test.ts b/test/accounts/managers/upgrademanager.test.ts similarity index 100% rename from new-test/accounts/managers/upgrademanager.test.ts rename to test/accounts/managers/upgrademanager.test.ts diff --git a/new-test/accounts/managers/validatormanager.test.ts b/test/accounts/managers/validatormanager.test.ts similarity index 100% rename from new-test/accounts/managers/validatormanager.test.ts rename to test/accounts/managers/validatormanager.test.ts diff --git a/new-test/accounts/transactions.test.ts b/test/accounts/transactions.test.ts similarity index 100% rename from new-test/accounts/transactions.test.ts rename to test/accounts/transactions.test.ts diff --git a/new-test/deployments/deployer.test.ts b/test/deployments/deployer.test.ts similarity index 100% rename from new-test/deployments/deployer.test.ts rename to test/deployments/deployer.test.ts diff --git a/new-test/deployments/rip7212.test.ts b/test/deployments/rip7212.test.ts similarity index 100% rename from new-test/deployments/rip7212.test.ts rename to test/deployments/rip7212.test.ts diff --git a/new-test/paymasters/erc20paymaster.test.ts b/test/paymasters/erc20paymaster.test.ts similarity index 100% rename from new-test/paymasters/erc20paymaster.test.ts rename to test/paymasters/erc20paymaster.test.ts diff --git a/new-test/paymasters/gaslesspaymaster.test.ts b/test/paymasters/gaslesspaymaster.test.ts similarity index 100% rename from new-test/paymasters/gaslesspaymaster.test.ts rename to test/paymasters/gaslesspaymaster.test.ts diff --git a/new-test/utils/deployer.ts b/test/utils/deployer.ts similarity index 100% rename from new-test/utils/deployer.ts rename to test/utils/deployer.ts diff --git a/new-test/utils/fixture.ts b/test/utils/fixture.ts similarity index 100% rename from new-test/utils/fixture.ts rename to test/utils/fixture.ts diff --git a/new-test/utils/managers/hookmanager.ts b/test/utils/managers/hookmanager.ts similarity index 100% rename from new-test/utils/managers/hookmanager.ts rename to test/utils/managers/hookmanager.ts diff --git a/new-test/utils/managers/modulemanager.ts b/test/utils/managers/modulemanager.ts similarity index 100% rename from new-test/utils/managers/modulemanager.ts rename to test/utils/managers/modulemanager.ts diff --git a/new-test/utils/managers/ownermanager.ts b/test/utils/managers/ownermanager.ts similarity index 100% rename from new-test/utils/managers/ownermanager.ts rename to test/utils/managers/ownermanager.ts diff --git a/new-test/utils/managers/upgrademanager.ts b/test/utils/managers/upgrademanager.ts similarity index 100% rename from new-test/utils/managers/upgrademanager.ts rename to test/utils/managers/upgrademanager.ts diff --git a/new-test/utils/managers/validatormanager.ts b/test/utils/managers/validatormanager.ts similarity index 100% rename from new-test/utils/managers/validatormanager.ts rename to test/utils/managers/validatormanager.ts diff --git a/new-test/utils/names.ts b/test/utils/names.ts similarity index 100% rename from new-test/utils/names.ts rename to test/utils/names.ts diff --git a/new-test/utils/p256.ts b/test/utils/p256.ts similarity index 100% rename from new-test/utils/p256.ts rename to test/utils/p256.ts diff --git a/new-test/utils/paymasters.ts b/test/utils/paymasters.ts similarity index 100% rename from new-test/utils/paymasters.ts rename to test/utils/paymasters.ts diff --git a/new-test/utils/snapshot.ts b/test/utils/snapshot.ts similarity index 100% rename from new-test/utils/snapshot.ts rename to test/utils/snapshot.ts diff --git a/new-test/utils/transactions.ts b/test/utils/transactions.ts similarity index 100% rename from new-test/utils/transactions.ts rename to test/utils/transactions.ts From 0706f0cf3537741bebd70d7237d6037bd49b32f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Tue, 17 Sep 2024 12:17:38 +0300 Subject: [PATCH 069/103] [clave-contracts] refactor: remove unused code --- test/utils/snapshot.ts | 35 ----------------------------------- 1 file changed, 35 deletions(-) delete mode 100644 test/utils/snapshot.ts diff --git a/test/utils/snapshot.ts b/test/utils/snapshot.ts deleted file mode 100644 index 9fa3221..0000000 --- a/test/utils/snapshot.ts +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright Clave - All Rights Reserved - * Unauthorized copying of this file, via any medium is strictly prohibited - * Proprietary and confidential - */ -import { appendFileSync } from 'fs'; -import { env } from 'process'; -import { read } from 'read-last-lines'; - -// Adds headers to snapshot file to specify what are the gas costs for -export async function snapshotHeader(header: string): Promise { - // Check if we are in snapshot mode - if (env.NODE_ENV == 'snapshot') { - // This prevents race conditions - while ( - !(await read('./.gas-snapshot', 1)).trim().endsWith('(opcodes)') - ) { - await sleep(50); - } - appendFileSync('./.gas-snapshot', `\n"${header}"\n`); - } -} - -export async function snapshotFirstHeader(header: string): Promise { - // Check if we are in snapshot mode - if (env.NODE_ENV === 'snapshot') { - appendFileSync('./.gas-snapshot', `\n"${header}"\n`); - } -} - -async function sleep(ms: number): Promise { - return new Promise((resolve) => { - setTimeout(resolve, ms); - }); -} From 550ba4a8af3909c00168caf0c4890a9ff590431b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Tue, 17 Sep 2024 19:35:55 +0300 Subject: [PATCH 070/103] fix: hre interface getter --- test/utils/deployer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/utils/deployer.ts b/test/utils/deployer.ts index 7cf7fb3..bc787c1 100644 --- a/test/utils/deployer.ts +++ b/test/utils/deployer.ts @@ -182,10 +182,10 @@ export class ClaveDeployer { const accountAddress = deployPromise[1]; const implementationInterface = ( - await this.hre.zksyncEthers.getContractFactory( + await this.hre.zksyncEthers.loadArtifact( CONTRACT_NAMES.IMPLEMENTATION, ) - ).interface; + ).abi; const account = new Contract( accountAddress, From 901f28e9ecfcc59721834d9d9c4e3ab3a948d981 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Tue, 17 Sep 2024 20:41:11 +0300 Subject: [PATCH 071/103] [clave-contracts] feat: add teevalidator test --- test/accounts/validators/teevalidator.test.ts | 152 ++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 test/accounts/validators/teevalidator.test.ts diff --git a/test/accounts/validators/teevalidator.test.ts b/test/accounts/validators/teevalidator.test.ts new file mode 100644 index 0000000..e6ccb62 --- /dev/null +++ b/test/accounts/validators/teevalidator.test.ts @@ -0,0 +1,152 @@ +/** + * Copyright Clave - All Rights Reserved + * Unauthorized copying of this file, via any medium is strictly prohibited + * Proprietary and confidential + */ +import { assert, expect } from 'chai'; +import type { ec } from 'elliptic'; +import { parseEther } from 'ethers'; +import * as hre from 'hardhat'; +import { Provider, Wallet, utils } from 'zksync-ethers'; +import type { Contract } from 'zksync-ethers'; +import type { TransactionLike } from 'zksync-ethers/build/types'; + +import { LOCAL_RICH_WALLETS, getWallet } from '../../../deploy/utils'; +import { ClaveDeployer } from '../../utils/deployer'; +import { fixture } from '../../utils/fixture'; +import { VALIDATORS } from '../../utils/names'; +import { + ethTransfer, + prepareMockTx, + prepareTeeTx, +} from '../../utils/transactions'; + +describe('Clave Contracts - Validator tests', () => { + let deployer: ClaveDeployer; + let provider: Provider; + let richWallet: Wallet; + let teeValidator: Contract; + let account: Contract; + let keyPair: ec.KeyPair; + + before(async () => { + richWallet = getWallet(hre, LOCAL_RICH_WALLETS[0].privateKey); + deployer = new ClaveDeployer(hre, richWallet); + provider = new Provider(hre.network.config.url, undefined, { + cacheTimeout: -1, + }); + + [, , , , teeValidator, account, keyPair] = await fixture( + deployer, + VALIDATORS.TEE, + ); + + const accountAddress = await account.getAddress(); + + await deployer.fund(10000, accountAddress); + }); + + describe('TEEValidator', () => { + it('should check existing validator', async () => { + const validatorAddress = await teeValidator.getAddress(); + + expect(await account.r1IsValidator(validatorAddress)).to.be.true; + }); + + describe('Signature checks', () => { + let richAddress: string; + let richBalanceBefore: bigint; + let amount: bigint; + + let txData: TransactionLike; + + beforeEach(async () => { + richBalanceBefore = await provider.getBalance(richAddress); + }); + + before(async () => { + richAddress = await richWallet.getAddress(); + + amount = parseEther('1'); + + txData = ethTransfer(richAddress, amount); + }); + describe('Valid tx and signature', () => { + it('should send a tx', async () => { + const tx = await prepareTeeTx( + provider, + account, + txData, + await teeValidator.getAddress(), + keyPair, + ); + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + await txReceipt.wait(); + + const richBalanceAfter = await provider.getBalance( + richAddress, + ); + expect(richBalanceAfter).to.eq(richBalanceBefore + amount); + }); + }); + + describe('Invalid tx and signature', () => { + it('should return false for a corrupted tx', async () => {}); + + it('should revert sending the tx', async () => { + const mockTxData = txData; + mockTxData.to = await Wallet.createRandom().getAddress(); // corrupted tx data + + const tx = await prepareTeeTx( + provider, + account, + txData, + await teeValidator.getAddress(), + keyPair, + ); + + try { + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + await txReceipt.wait(); + assert(false); + } catch (err) {} + + const richBalanceAfter = await provider.getBalance( + richAddress, + ); + expect(richBalanceAfter).to.eq(richBalanceBefore); + }); + }); + + describe('Tx and wrong signature', () => { + it('should return false for wrong signature in the tx', async () => {}); + + it('should revert sending the tx', async () => { + const tx = await prepareMockTx( + provider, + account, + txData, + await teeValidator.getAddress(), + ); + + try { + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + await txReceipt.wait(); + assert(false); + } catch (err) {} + + const richBalanceAfter = await provider.getBalance( + richAddress, + ); + expect(richBalanceAfter).to.eq(richBalanceBefore); + }); + }); + }); + }); +}); From cf0f3723cd21859682e2afc60572f6068364a740 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Tue, 17 Sep 2024 21:16:04 +0300 Subject: [PATCH 072/103] [clave-contracts] feat: init eoavalidator test --- test/accounts/validators/eoavalidator.test.ts | 136 ++++++++++++++++++ test/accounts/validators/teevalidator.test.ts | 5 +- test/utils/transactions.ts | 47 +++++- 3 files changed, 183 insertions(+), 5 deletions(-) create mode 100644 test/accounts/validators/eoavalidator.test.ts diff --git a/test/accounts/validators/eoavalidator.test.ts b/test/accounts/validators/eoavalidator.test.ts new file mode 100644 index 0000000..dcde14b --- /dev/null +++ b/test/accounts/validators/eoavalidator.test.ts @@ -0,0 +1,136 @@ +/** + * Copyright Clave - All Rights Reserved + * Unauthorized copying of this file, via any medium is strictly prohibited + * Proprietary and confidential + */ +import { assert, expect } from 'chai'; +import type { ec } from 'elliptic'; +import type { HDNodeWallet } from 'ethers'; +import { parseEther } from 'ethers'; +import * as hre from 'hardhat'; +import { Provider, Wallet, utils } from 'zksync-ethers'; +import type { Contract } from 'zksync-ethers'; +import type { TransactionLike } from 'zksync-ethers/build/types'; + +import { LOCAL_RICH_WALLETS, getWallet } from '../../../deploy/utils'; +import { ClaveDeployer } from '../../utils/deployer'; +import { fixture } from '../../utils/fixture'; +import { addK1Key } from '../../utils/managers/ownermanager'; +import { addK1Validator } from '../../utils/managers/validatormanager'; +import { VALIDATORS } from '../../utils/names'; +import { ethTransfer, prepareEOATx } from '../../utils/transactions'; + +describe('Clave Contracts - Validator tests', () => { + let deployer: ClaveDeployer; + let provider: Provider; + let richWallet: Wallet; + let teeValidator: Contract; + let account: Contract; + let keyPair: ec.KeyPair; + + before(async () => { + richWallet = getWallet(hre, LOCAL_RICH_WALLETS[0].privateKey); + deployer = new ClaveDeployer(hre, richWallet); + provider = new Provider(hre.network.config.url, undefined, { + cacheTimeout: -1, + }); + + [, , , , teeValidator, account, keyPair] = await fixture( + deployer, + VALIDATORS.TEE, + ); + + const accountAddress = await account.getAddress(); + + await deployer.fund(10000, accountAddress); + }); + + describe('EOAValidator', () => { + let newK1Validator: Contract; + let newK1Owner: HDNodeWallet; + + before(async () => { + newK1Validator = await deployer.validator(VALIDATORS.EOA); + newK1Owner = Wallet.createRandom(); + + await addK1Validator( + provider, + account, + teeValidator, + newK1Validator, + keyPair, + ); + + await addK1Key( + provider, + account, + teeValidator, + await newK1Owner.getAddress(), + keyPair, + ); + }); + + it('should check existing validator', async () => { + const teeValidatorAddress = await teeValidator.getAddress(); + const k1ValidatorAddress = await newK1Validator.getAddress(); + const k1OwnerAddress = await newK1Owner.getAddress(); + + expect(await account.r1IsValidator(teeValidatorAddress)).to.be.true; + + expect(await account.k1IsValidator(k1ValidatorAddress)).to.be.true; + + expect(await account.k1IsOwner(k1OwnerAddress)).to.be.true; + }); + + describe('Signature checks', () => { + let richAddress: string; + let richBalanceBefore: bigint; + let amount: bigint; + + let txData: TransactionLike; + + beforeEach(async () => { + richBalanceBefore = await provider.getBalance(richAddress); + }); + + before(async () => { + richAddress = await richWallet.getAddress(); + + amount = parseEther('1'); + + txData = ethTransfer(richAddress, amount); + + Wallet.createRandom(); + }); + + describe('Valid tx and signature', () => { + it.only('should send a tx', async () => { + const tx = await prepareEOATx( + provider, + account, + txData, + await newK1Validator.getAddress(), + newK1Owner, + ); + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + await txReceipt.wait(); + + const richBalanceAfter = await provider.getBalance( + richAddress, + ); + expect(richBalanceAfter).to.eq(richBalanceBefore + amount); + }); + }); + + describe('Invalid tx and signature', () => { + it('should revert sending the tx', async () => {}); + }); + + describe('Tx and wrong signature', () => { + it('should revert sending the tx', async () => {}); + }); + }); + }); +}); diff --git a/test/accounts/validators/teevalidator.test.ts b/test/accounts/validators/teevalidator.test.ts index e6ccb62..f526148 100644 --- a/test/accounts/validators/teevalidator.test.ts +++ b/test/accounts/validators/teevalidator.test.ts @@ -71,6 +71,7 @@ describe('Clave Contracts - Validator tests', () => { txData = ethTransfer(richAddress, amount); }); + describe('Valid tx and signature', () => { it('should send a tx', async () => { const tx = await prepareTeeTx( @@ -93,8 +94,6 @@ describe('Clave Contracts - Validator tests', () => { }); describe('Invalid tx and signature', () => { - it('should return false for a corrupted tx', async () => {}); - it('should revert sending the tx', async () => { const mockTxData = txData; mockTxData.to = await Wallet.createRandom().getAddress(); // corrupted tx data @@ -123,8 +122,6 @@ describe('Clave Contracts - Validator tests', () => { }); describe('Tx and wrong signature', () => { - it('should return false for wrong signature in the tx', async () => {}); - it('should revert sending the tx', async () => { const tx = await prepareMockTx( provider, diff --git a/test/utils/transactions.ts b/test/utils/transactions.ts index 743260b..a7f9fa3 100644 --- a/test/utils/transactions.ts +++ b/test/utils/transactions.ts @@ -4,7 +4,7 @@ * Proprietary and confidential */ import type { ec } from 'elliptic'; -import type { BigNumberish } from 'ethers'; +import type { BigNumberish, HDNodeWallet } from 'ethers'; import { ethers, parseEther, sha256 } from 'ethers'; import type { Contract, Provider, types } from 'zksync-ethers'; import { EIP712Signer, utils } from 'zksync-ethers'; @@ -214,3 +214,48 @@ export async function prepareBatchTx( return tx; } + +export async function prepareEOATx( + provider: Provider, + account: Contract, + tx: types.TransactionLike, + validatorAddress: string, + wallet: HDNodeWallet, + hookData: Array = [], + paymasterParams?: types.PaymasterParams, +): Promise { + if (tx.value == undefined) { + tx.value = parseEther('0'); + } + + tx = { + ...tx, + from: await account.getAddress(), + nonce: await provider.getTransactionCount(await account.getAddress()), + gasLimit: 30_000_000, + gasPrice: await provider.getGasPrice(), + chainId: (await provider.getNetwork()).chainId, + type: 113, + customData: { + gasPerPubdata: utils.DEFAULT_GAS_PER_PUBDATA_LIMIT, + paymasterParams: paymasterParams, + } as types.Eip712Meta, + }; + + const signedTxHash = EIP712Signer.getSignedDigest(tx); + + const abiCoder = ethers.AbiCoder.defaultAbiCoder(); + let signature = await wallet.signMessage(signedTxHash); + + signature = abiCoder.encode( + ['bytes', 'address', 'bytes[]'], + [signature, validatorAddress, hookData], + ); + + tx.customData = { + ...tx.customData, + customSignature: signature, + }; + + return tx; +} From dec30610df3b6ca210bf6ad6ce8c37aab8e2734d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Tue, 17 Sep 2024 22:28:39 +0300 Subject: [PATCH 073/103] [clave-contracts] feat: depracate subsidizer paymaster --- contracts/paymasters/SubsidizerPaymaster.sol | 305 ------------------ contracts/test/SubsidizerPaymasterMock.sol | 306 ------------------- 2 files changed, 611 deletions(-) delete mode 100644 contracts/paymasters/SubsidizerPaymaster.sol delete mode 100644 contracts/test/SubsidizerPaymasterMock.sol diff --git a/contracts/paymasters/SubsidizerPaymaster.sol b/contracts/paymasters/SubsidizerPaymaster.sol deleted file mode 100644 index c2d1aec..0000000 --- a/contracts/paymasters/SubsidizerPaymaster.sol +++ /dev/null @@ -1,305 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.17; - -import {IPaymaster, ExecutionResult, PAYMASTER_VALIDATION_SUCCESS_MAGIC} from '@matterlabs/zksync-contracts/l2/system-contracts/interfaces/IPaymaster.sol'; -import {IPaymasterFlow} from '@matterlabs/zksync-contracts/l2/system-contracts/interfaces/IPaymasterFlow.sol'; -import {Transaction} from '@matterlabs/zksync-contracts/l2/system-contracts/libraries/TransactionHelper.sol'; -import {BOOTLOADER_FORMAL_ADDRESS} from '@matterlabs/zksync-contracts/l2/system-contracts/Constants.sol'; -import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol'; -import {PrimaryProdDataServiceConsumerBase} from '@redstone-finance/evm-connector/contracts/data-services/PrimaryProdDataServiceConsumerBase.sol'; -import {Errors} from '../libraries/Errors.sol'; -import {IERC20, SafeERC20} from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol'; -import {BootloaderAuth} from '../auth/BootloaderAuth.sol'; -import {IClaveRegistry} from '../interfaces/IClaveRegistry.sol'; - -// Allowed ERC-20 tokens data struct input -struct TokenInput { - address tokenAddress; // token addresses - uint32 decimals; // decimals of the tokens - uint256 priceMarkup; // price markup percentage -} - -// Stored token data including oracle ids and price markups -struct TokenData { - uint32 decimals; // decimals of the tokens - uint192 markup; // price markup percentage -} - -/** - * @title SubsidizerPaymaster is a super of the ERC20Paymaster to pay for transaction fees in allowed ERC-20 tokens (stablecoins) by subsidizing a specific amount - * @author https://getclave.io - * @dev This contract uses Redstone oracle to get token prices, please check [Redstone docs](https://docs.redstone.finance/docs/smart-contract-devs/get-started/redstone-core). - */ -contract SubsidizerPaymaster is - IPaymaster, - PrimaryProdDataServiceConsumerBase, - Ownable, - BootloaderAuth -{ - // Using OpenZeppelin's SafeERC20 library to perform token transfers - using SafeERC20 for IERC20; - - // The nominator used for price calculation - uint256 constant PRICE_PAIR_NOMINATOR = 1e18; - // The nominator used for markup calculation - uint256 constant MARKUP_NOMINATOR = 1e6; - //The nominator used to get rid of oracle price decimals - uint256 constant ORACLE_NOMINATOR = 1e8; - // Maximum subsidized fee amount - uint256 constant MAX_GAS_TO_SUBSIDIZE = 1_250_000; - - // Clave account registry contract - address public claveRegistry; - - // Store allowed tokens addresses with their data - mapping(address => TokenData) public allowedTokens; - - // Event to be emitted when a token is used to pay for transaction - event SubsidizerPaymasterUsed(address indexed user, address token); - // Event to be emitted when a new token is allowed - event ERC20TokenAllowed(address token); - // Event to be emitted when a new token is removed - event ERC20TokenRemoved(address token); - // Event to be emitted when the balance is withdrawn - event BalanceWithdrawn(address to, uint256 amount); - - /** - * @notice Constructor function - * @param tokens TokenInput[] - Array of token addresses, markups, and their oracle ids in string - * @dev Use markup by adding percentage amount to 100, e.g. 10% markup will be 11000 - * @dev Make sure to use true decimal values for the tokens, otherwise the conversions will be incorrect - */ - constructor(TokenInput[] memory tokens, address registry) { - for (uint256 i = 0; i < tokens.length; i++) { - // Decline zero-addresses - if (tokens[i].tokenAddress == address(0)) revert Errors.INVALID_TOKEN(); - - // Decline false markup values - if (tokens[i].priceMarkup < 5000 || tokens[i].priceMarkup >= 100000) - revert Errors.INVALID_TOKEN(); - uint192 priceMarkup = uint192(tokens[i].priceMarkup * (MARKUP_NOMINATOR / 1e4)); - - allowedTokens[tokens[i].tokenAddress] = TokenData(tokens[i].decimals, priceMarkup); - } - - claveRegistry = registry; - } - - // Allow receiving ETH - receive() external payable {} - - /// @inheritdoc IPaymaster - /// @dev return the fee payer token address in the context - function validateAndPayForPaymasterTransaction( - bytes32 /**_txHash*/, - bytes32 /**_suggestedSignedHash*/, - Transaction calldata _transaction - ) external payable onlyBootloader returns (bytes4 magic, bytes memory context) { - // By default we consider the transaction as accepted. - magic = PAYMASTER_VALIDATION_SUCCESS_MAGIC; - - // Get the user address - address userAddress = address(uint160(_transaction.from)); - - // Check if the account is a Clave account - if (!IClaveRegistry(claveRegistry).isClave(userAddress)) revert Errors.NOT_CLAVE_ACCOUNT(); - - // Revert if standart paymaster input is shorter than 4 bytes - if (_transaction.paymasterInput.length < 4) revert Errors.SHORT_PAYMASTER_INPUT(); - - // Check the paymaster input selector to detect flow - bytes4 paymasterInputSelector = bytes4(_transaction.paymasterInput[0:4]); - if (paymasterInputSelector != IPaymasterFlow.approvalBased.selector) - revert Errors.UNSUPPORTED_FLOW(); - - // Extract token address and oracle payload from the paymaster input and check if it is allowed - (address token, , bytes memory oracleCalldata) = abi.decode( - _transaction.paymasterInput[4:], - (address, uint256, bytes) - ); - if (allowedTokens[token].decimals == uint32(0)) revert Errors.UNSUPPORTED_TOKEN(); - - address thisAddress = address(this); - uint256 providedAllowance = IERC20(token).allowance(userAddress, thisAddress); - - // Required ETH to pay fees - uint256 requiredETH = _transaction.gasLimit * _transaction.maxFeePerGas; - // Required ETH to receive from the user - uint256 userToPayETH; - if (_transaction.gasLimit > MAX_GAS_TO_SUBSIDIZE) { - userToPayETH = - (_transaction.gasLimit - MAX_GAS_TO_SUBSIDIZE) * - _transaction.maxFeePerGas; - } else { - userToPayETH = 0; - } - - // Conversion rate of ETH to token - // 0, if not receiving tokens from user - uint256 rate; - - if (userToPayETH > 0) { - rate = getPairPrice(token, oracleCalldata); - // Calculated fee amount as token - uint256 requiredToken = (userToPayETH * rate) / PRICE_PAIR_NOMINATOR; - - // Check token allowance for the fee - if (providedAllowance < requiredToken) revert Errors.LESS_ALLOWANCE_FOR_PAYMASTER(); - - // Transfer token to the fee collector - IERC20(token).safeTransferFrom(userAddress, address(this), requiredToken); - } - - // Transfer fees to the bootloader - (bool feeSuccess, ) = payable(BOOTLOADER_FORMAL_ADDRESS).call{value: requiredETH}(''); - if (!feeSuccess) revert Errors.FAILED_FEE_TRANSFER(); - - // Use fee token address as context - context = abi.encode(token, rate); - } - - /// @inheritdoc IPaymaster - function postTransaction( - bytes calldata _context, - Transaction calldata _transaction, - bytes32 /**_txHash*/, - bytes32 /**_suggestedSignedHash*/, - ExecutionResult /**_txResult*/, - uint256 _maxRefundedGas - ) external payable onlyBootloader { - (address tokenAddress, uint256 rate) = abi.decode(_context, (address, uint256)); - address fromAddress = address(uint160(_transaction.from)); - - uint256 refundAmount = calcRefundAmount( - _transaction.gasLimit, - _maxRefundedGas, - _transaction.maxFeePerGas, - rate - ); - - if (refundAmount > 0) IERC20(tokenAddress).safeTransfer(fromAddress, refundAmount); - - // Emit user address with fee payer token - emit SubsidizerPaymasterUsed(fromAddress, tokenAddress); - } - - /** - * @notice Allow a new token to be used in paymaster - * @param token TokenInput calldata - Token address and its oracle ids in string of allowed token - * @dev Only owner address can call this method - * @dev Make sure to use true decimal values for the tokens, otherwise the conversions will be incorrect - */ - function allowToken(TokenInput calldata token) external onlyOwner { - // Skip zero-addresses - if (token.tokenAddress == address(0)) { - revert Errors.INVALID_TOKEN(); - } - - // Skip false markup values - if (token.priceMarkup < 5000 || token.priceMarkup >= 100000) revert Errors.INVALID_MARKUP(); - uint192 priceMarkup = uint192(token.priceMarkup * (MARKUP_NOMINATOR / 1e4)); - - allowedTokens[token.tokenAddress] = TokenData(token.decimals, uint192(priceMarkup)); - emit ERC20TokenAllowed(token.tokenAddress); - } - - /** - * @notice Remove allowed paymaster tokens - * @param tokenAddress address - Token address to be removed - * @dev Only owner address can call this method - */ - function removeToken(address tokenAddress) external onlyOwner { - delete allowedTokens[tokenAddress]; - emit ERC20TokenRemoved(tokenAddress); - } - - /** - * @notice Withdraw paymaster funds as owner - * @param to address - Token receiver address - * @param amount uint256 - Amount to be withdrawn - * @dev Only owner address can call this method - */ - function withdraw(address to, uint256 amount) external onlyOwner { - // Send paymaster funds to the given address - (bool success, ) = payable(to).call{value: amount}(''); - if (!success) revert Errors.UNAUTHORIZED_WITHDRAW(); - - emit BalanceWithdrawn(to, amount); - } - - /** - * @notice Withdraw paymaster token funds as owner - * @param token address - Token address to withdraw - * @param to address - Token receiver address - * @param amount uint256 - Amount to be withdrawn - * @dev Only owner address can call this method - */ - function withdrawToken(address token, address to, uint256 amount) external onlyOwner { - // Send paymaster funds to the given address - IERC20(token).safeTransfer(to, amount); - } - - /** - * @notice This function calls the oracle and returns the values - * @param - bytes - Oracle manuel payload - * @return uint256 - Oracle return as token price - * @dev bytes parameter is extracted from the paymaster input by the oracle contract, so it is NOT UNUSED here - * @dev This function should be called by an external call inside this contract to pass its specific calldata - */ - function callOracle( - bytes memory //* oracleCalldata */ - ) external view returns (uint256) { - return getOracleNumericValueFromTxMsg(bytes32('ETH')); - } - - /** - * @notice This function gets the ETH/PARAM_TOKEN price from the oracle - * @param token address - Token address - * @param oracleCalldata bytes - Oracle calldata for Redstone - * @return rate uint256 - ETH/TOKEN price - * @dev TOKEN price is accepted as 1 $ - */ - function getPairPrice( - address token, - bytes memory oracleCalldata - ) private view returns (uint256 rate) { - // Used asset decimals - uint256 tokenDecimals = 10 ** (allowedTokens[token].decimals); - // Oracle value - uint256 value = this.callOracle(oracleCalldata); - - // Calculated token price - rate = - (value * allowedTokens[token].markup * tokenDecimals) / - (ORACLE_NOMINATOR * MARKUP_NOMINATOR); - } - - /** - * @notice This function calculates the gas refund amount for the user - * @param gasLimit uint256 - Gas limit of the transaction - * @param gasRefunded uint256 - Unused gas amount - * @param maxFeePerGas uint256 - Gas price from zkSync transaction - * @param rate uint256 - Conversion rate regarding to ETH/Token pair - * @return refundTokenAmount uint256 - Refund amount as token - */ - function calcRefundAmount( - uint256 gasLimit, - uint256 gasRefunded, - uint256 maxFeePerGas, - uint256 rate - ) private pure returns (uint256 refundTokenAmount) { - if (!(rate > 0)) return refundTokenAmount; - - if (gasLimit > MAX_GAS_TO_SUBSIDIZE) { - uint256 userPaidGas = gasLimit - MAX_GAS_TO_SUBSIDIZE; - uint256 gasUsed = gasLimit - gasRefunded; - - // If, gasUsed is less than we want to pay, compensate user - // Else, we can pay (userPaidGas - (gasUsed - MAX_FEE_TO_SUBSIDIZE)) at best, which can - // be simplified to _maxRefundedGas - uint256 refundGas = MAX_GAS_TO_SUBSIDIZE > gasUsed ? userPaidGas : gasRefunded; - - refundTokenAmount = (refundGas * maxFeePerGas * rate) / PRICE_PAIR_NOMINATOR; - } - } -} diff --git a/contracts/test/SubsidizerPaymasterMock.sol b/contracts/test/SubsidizerPaymasterMock.sol deleted file mode 100644 index 56dc5bf..0000000 --- a/contracts/test/SubsidizerPaymasterMock.sol +++ /dev/null @@ -1,306 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.17; - -import {IPaymaster, ExecutionResult, PAYMASTER_VALIDATION_SUCCESS_MAGIC} from '@matterlabs/zksync-contracts/l2/system-contracts/interfaces/IPaymaster.sol'; -import {IPaymasterFlow} from '@matterlabs/zksync-contracts/l2/system-contracts/interfaces/IPaymasterFlow.sol'; -import {Transaction} from '@matterlabs/zksync-contracts/l2/system-contracts/libraries/TransactionHelper.sol'; -import {BOOTLOADER_FORMAL_ADDRESS} from '@matterlabs/zksync-contracts/l2/system-contracts/Constants.sol'; -import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol'; -import {PrimaryProdDataServiceConsumerBase} from '@redstone-finance/evm-connector/contracts/data-services/PrimaryProdDataServiceConsumerBase.sol'; -import {Errors} from '../libraries/Errors.sol'; -import {IERC20, SafeERC20} from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol'; -import {BootloaderAuth} from '../auth/BootloaderAuth.sol'; -import {IClaveRegistry} from '../interfaces/IClaveRegistry.sol'; - -// Allowed ERC-20 tokens data struct input -struct TokenInput { - address tokenAddress; // token addresses - uint32 decimals; // decimals of the tokens - uint256 priceMarkup; // price markup percentage -} - -// Stored token data including oracle ids and price markups -struct TokenData { - uint32 decimals; // decimals of the tokens - uint192 markup; // price markup percentage -} - -/** - * @title SubsidizerPaymaster is a super of the ERC20Paymaster to pay for transaction fees in allowed ERC-20 tokens (stablecoins) by subsidizing a specific amount - * @author https://getclave.io - * @dev This contract uses Redstone oracle to get token prices, please check [Redstone docs](https://docs.redstone.finance/docs/smart-contract-devs/get-started/redstone-core). - */ -contract SubsidizerPaymasterMock is - IPaymaster, - PrimaryProdDataServiceConsumerBase, - Ownable, - BootloaderAuth -{ - // Using OpenZeppelin's SafeERC20 library to perform token transfers - using SafeERC20 for IERC20; - - // The nominator used for price calculation - uint256 constant PRICE_PAIR_NOMINATOR = 1e18; - // The nominator used for markup calculation - uint256 constant MARKUP_NOMINATOR = 1e6; - //The nominator used to get rid of oracle price decimals - uint256 constant ORACLE_NOMINATOR = 1e8; - // Maximum subsidized fee amount - uint256 constant MAX_GAS_TO_SUBSIDIZE = 1_250_000; - - // Clave account registry contract - address public claveRegistry; - - // Store allowed tokens addresses with their data - mapping(address => TokenData) public allowedTokens; - - // Event to be emitted when a token is used to pay for transaction - event SubsidizerPaymasterUsed(address indexed user, address token); - // Event to be emitted when a new token is allowed - event ERC20TokenAllowed(address token); - // Event to be emitted when a new token is removed - event ERC20TokenRemoved(address token); - // Event to be emitted when the balance is withdrawn - event BalanceWithdrawn(address to, uint256 amount); - - /** - * @notice Constructor function - * @param tokens TokenInput[] - Array of token addresses, markups, and their oracle ids in string - * @dev Use markup by adding percentage amount to 100, e.g. 10% markup will be 11000 - * @dev Make sure to use true decimal values for the tokens, otherwise the conversions will be incorrect - */ - constructor(TokenInput[] memory tokens, address registry) { - for (uint256 i = 0; i < tokens.length; i++) { - // Decline zero-addresses - if (tokens[i].tokenAddress == address(0)) revert Errors.INVALID_TOKEN(); - - // Decline false markup values - if (tokens[i].priceMarkup < 5000 || tokens[i].priceMarkup >= 100000) - revert Errors.INVALID_TOKEN(); - uint192 priceMarkup = uint192(tokens[i].priceMarkup * (MARKUP_NOMINATOR / 1e4)); - - allowedTokens[tokens[i].tokenAddress] = TokenData(tokens[i].decimals, priceMarkup); - } - - claveRegistry = registry; - } - - // Allow receiving ETH - receive() external payable {} - - /// @inheritdoc IPaymaster - /// @dev return the fee payer token address in the context - function validateAndPayForPaymasterTransaction( - bytes32 /**_txHash*/, - bytes32 /**_suggestedSignedHash*/, - Transaction calldata _transaction - ) external payable onlyBootloader returns (bytes4 magic, bytes memory context) { - // By default we consider the transaction as accepted. - magic = PAYMASTER_VALIDATION_SUCCESS_MAGIC; - - // Get the user address - address userAddress = address(uint160(_transaction.from)); - - // Check if the account is a Clave account - if (!IClaveRegistry(claveRegistry).isClave(userAddress)) revert Errors.NOT_CLAVE_ACCOUNT(); - - // Revert if standart paymaster input is shorter than 4 bytes - if (_transaction.paymasterInput.length < 4) revert Errors.SHORT_PAYMASTER_INPUT(); - - // Check the paymaster input selector to detect flow - bytes4 paymasterInputSelector = bytes4(_transaction.paymasterInput[0:4]); - if (paymasterInputSelector != IPaymasterFlow.approvalBased.selector) - revert Errors.UNSUPPORTED_FLOW(); - - // Extract token address and oracle payload from the paymaster input and check if it is allowed - (address token, , bytes memory oracleCalldata) = abi.decode( - _transaction.paymasterInput[4:], - (address, uint256, bytes) - ); - if (allowedTokens[token].decimals == uint32(0)) revert Errors.UNSUPPORTED_TOKEN(); - - address thisAddress = address(this); - uint256 providedAllowance = IERC20(token).allowance(userAddress, thisAddress); - - // Required ETH to pay fees - uint256 requiredETH = _transaction.gasLimit * _transaction.maxFeePerGas; - // Required ETH to receive from the user - uint256 userToPayETH; - if (_transaction.gasLimit > MAX_GAS_TO_SUBSIDIZE) { - userToPayETH = - (_transaction.gasLimit - MAX_GAS_TO_SUBSIDIZE) * - _transaction.maxFeePerGas; - } else { - userToPayETH = 0; - } - - // Conversion rate of ETH to token - // 0, if not receiving tokens from user - uint256 rate; - - if (userToPayETH > 0) { - rate = getPairPrice(token, oracleCalldata); - // Calculated fee amount as token - uint256 requiredToken = (userToPayETH * rate) / PRICE_PAIR_NOMINATOR; - - // Check token allowance for the fee - if (providedAllowance < requiredToken) revert Errors.LESS_ALLOWANCE_FOR_PAYMASTER(); - - // Transfer token to the fee collector - IERC20(token).safeTransferFrom(userAddress, address(this), requiredToken); - } - - // Transfer fees to the bootloader - (bool feeSuccess, ) = payable(BOOTLOADER_FORMAL_ADDRESS).call{value: requiredETH}(''); - if (!feeSuccess) revert Errors.FAILED_FEE_TRANSFER(); - - // Use fee token address as context - context = abi.encode(token, rate); - } - - /// @inheritdoc IPaymaster - function postTransaction( - bytes calldata _context, - Transaction calldata _transaction, - bytes32 /**_txHash*/, - bytes32 /**_suggestedSignedHash*/, - ExecutionResult /**_txResult*/, - uint256 _maxRefundedGas - ) external payable onlyBootloader { - (address tokenAddress, uint256 rate) = abi.decode(_context, (address, uint256)); - address fromAddress = address(uint160(_transaction.from)); - - uint256 refundAmount = calcRefundAmount( - _transaction.gasLimit, - _maxRefundedGas, - _transaction.maxFeePerGas, - rate - ); - - if (refundAmount > 0) IERC20(tokenAddress).safeTransfer(fromAddress, refundAmount); - - // Emit user address with fee payer token - emit SubsidizerPaymasterUsed(fromAddress, tokenAddress); - } - - /** - * @notice Allow a new token to be used in paymaster - * @param token TokenInput calldata - Token address and its oracle ids in string of allowed token - * @dev Only owner address can call this method - * @dev Make sure to use true decimal values for the tokens, otherwise the conversions will be incorrect - */ - function allowToken(TokenInput calldata token) external onlyOwner { - // Skip zero-addresses - if (token.tokenAddress == address(0)) { - revert Errors.INVALID_TOKEN(); - } - - // Skip false markup values - if (token.priceMarkup < 5000 || token.priceMarkup >= 100000) revert Errors.INVALID_MARKUP(); - uint192 priceMarkup = uint192(token.priceMarkup * (MARKUP_NOMINATOR / 1e4)); - - allowedTokens[token.tokenAddress] = TokenData(token.decimals, uint192(priceMarkup)); - emit ERC20TokenAllowed(token.tokenAddress); - } - - /** - * @notice Remove allowed paymaster tokens - * @param tokenAddress address - Token address to be removed - * @dev Only owner address can call this method - */ - function removeToken(address tokenAddress) external onlyOwner { - delete allowedTokens[tokenAddress]; - emit ERC20TokenRemoved(tokenAddress); - } - - /** - * @notice Withdraw paymaster funds as owner - * @param to address - Token receiver address - * @param amount uint256 - Amount to be withdrawn - * @dev Only owner address can call this method - */ - function withdraw(address to, uint256 amount) external onlyOwner { - // Send paymaster funds to the given address - (bool success, ) = payable(to).call{value: amount}(''); - if (!success) revert Errors.UNAUTHORIZED_WITHDRAW(); - - emit BalanceWithdrawn(to, amount); - } - - /** - * @notice Withdraw paymaster token funds as owner - * @param token address - Token address to withdraw - * @param to address - Token receiver address - * @param amount uint256 - Amount to be withdrawn - * @dev Only owner address can call this method - */ - function withdrawToken(address token, address to, uint256 amount) external onlyOwner { - // Send paymaster funds to the given address - IERC20(token).safeTransfer(to, amount); - } - - /** - * @notice This function calls the oracle and returns the values - * @param - bytes - Oracle manuel payload - * @return uint256 - Oracle return as token price - * @dev bytes parameter is extracted from the paymaster input by the oracle contract, so it is NOT UNUSED here - * @dev This function should be called by an external call inside this contract to pass its specific calldata - */ - function callOracle( - bytes memory //* oracleCalldata */ - ) external view returns (uint256) { - // return getOracleNumericValueFromTxMsg(bytes32('ETH')); - return 1500 * ORACLE_NOMINATOR; - } - - /** - * @notice This function gets the ETH/PARAM_TOKEN price from the oracle - * @param token address - Token address - * @param oracleCalldata bytes - Oracle calldata for Redstone - * @return rate uint256 - ETH/TOKEN price - * @dev TOKEN price is accepted as 1 $ - */ - function getPairPrice( - address token, - bytes memory oracleCalldata - ) public view returns (uint256 rate) { - // Used asset decimals - uint256 tokenDecimals = 10 ** (allowedTokens[token].decimals); - // Oracle value - uint256 value = this.callOracle(oracleCalldata); - - // Calculated token price - rate = - (value * allowedTokens[token].markup * tokenDecimals) / - (ORACLE_NOMINATOR * MARKUP_NOMINATOR); - } - - /** - * @notice This function calculates the gas refund amount for the user - * @param gasLimit uint256 - Gas limit of the transaction - * @param gasRefunded uint256 - Unused gas amount - * @param maxFeePerGas uint256 - Gas price from zkSync transaction - * @param rate uint256 - Conversion rate regarding to ETH/Token pair - * @return refundTokenAmount uint256 - Refund amount as token - */ - function calcRefundAmount( - uint256 gasLimit, - uint256 gasRefunded, - uint256 maxFeePerGas, - uint256 rate - ) public pure returns (uint256 refundTokenAmount) { - if (!(rate > 0)) return refundTokenAmount; - - if (gasLimit > MAX_GAS_TO_SUBSIDIZE) { - uint256 userPaidGas = gasLimit - MAX_GAS_TO_SUBSIDIZE; - uint256 gasUsed = gasLimit - gasRefunded; - - // If, gasUsed is less than we want to pay, compensate user - // Else, we can pay (userPaidGas - (gasUsed - MAX_FEE_TO_SUBSIDIZE)) at best, which can - // be simplified to _maxRefundedGas - uint256 refundGas = MAX_GAS_TO_SUBSIDIZE > gasUsed ? userPaidGas : gasRefunded; - - refundTokenAmount = (refundGas * maxFeePerGas * rate) / PRICE_PAIR_NOMINATOR; - } - } -} From 1f123f6d16a28b0f24eb732e64f6fb105a714ce0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Wed, 18 Sep 2024 13:24:43 +0300 Subject: [PATCH 074/103] [clave-contracts] refactor: use sync k1 signer --- test/accounts/validators/eoavalidator.test.ts | 2 -- test/utils/transactions.ts | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/test/accounts/validators/eoavalidator.test.ts b/test/accounts/validators/eoavalidator.test.ts index dcde14b..bff3142 100644 --- a/test/accounts/validators/eoavalidator.test.ts +++ b/test/accounts/validators/eoavalidator.test.ts @@ -99,8 +99,6 @@ describe('Clave Contracts - Validator tests', () => { amount = parseEther('1'); txData = ethTransfer(richAddress, amount); - - Wallet.createRandom(); }); describe('Valid tx and signature', () => { diff --git a/test/utils/transactions.ts b/test/utils/transactions.ts index a7f9fa3..a5a577a 100644 --- a/test/utils/transactions.ts +++ b/test/utils/transactions.ts @@ -245,7 +245,7 @@ export async function prepareEOATx( const signedTxHash = EIP712Signer.getSignedDigest(tx); const abiCoder = ethers.AbiCoder.defaultAbiCoder(); - let signature = await wallet.signMessage(signedTxHash); + let signature = wallet.signMessageSync(signedTxHash); signature = abiCoder.encode( ['bytes', 'address', 'bytes[]'], From 9e47982e19e717121ed5c7299865ca8f4c99b03e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Wed, 18 Sep 2024 14:23:15 +0300 Subject: [PATCH 075/103] [clave-contracts] fix: eoa signing for tx --- test/utils/transactions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/utils/transactions.ts b/test/utils/transactions.ts index a5a577a..8b4373a 100644 --- a/test/utils/transactions.ts +++ b/test/utils/transactions.ts @@ -245,7 +245,7 @@ export async function prepareEOATx( const signedTxHash = EIP712Signer.getSignedDigest(tx); const abiCoder = ethers.AbiCoder.defaultAbiCoder(); - let signature = wallet.signMessageSync(signedTxHash); + let signature = wallet.signingKey.sign(signedTxHash).serialized; signature = abiCoder.encode( ['bytes', 'address', 'bytes[]'], From 5a478ea6efb1630ced59e11c02044180fdaf73a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Wed, 18 Sep 2024 14:27:01 +0300 Subject: [PATCH 076/103] [clave-contracts] feat: complete eoa validator tests --- test/accounts/validators/eoavalidator.test.ts | 56 +++++++++++++++++-- 1 file changed, 52 insertions(+), 4 deletions(-) diff --git a/test/accounts/validators/eoavalidator.test.ts b/test/accounts/validators/eoavalidator.test.ts index bff3142..4dcb7d4 100644 --- a/test/accounts/validators/eoavalidator.test.ts +++ b/test/accounts/validators/eoavalidator.test.ts @@ -18,7 +18,11 @@ import { fixture } from '../../utils/fixture'; import { addK1Key } from '../../utils/managers/ownermanager'; import { addK1Validator } from '../../utils/managers/validatormanager'; import { VALIDATORS } from '../../utils/names'; -import { ethTransfer, prepareEOATx } from '../../utils/transactions'; +import { + ethTransfer, + prepareEOATx, + prepareMockTx, +} from '../../utils/transactions'; describe('Clave Contracts - Validator tests', () => { let deployer: ClaveDeployer; @@ -102,7 +106,7 @@ describe('Clave Contracts - Validator tests', () => { }); describe('Valid tx and signature', () => { - it.only('should send a tx', async () => { + it('should send a tx', async () => { const tx = await prepareEOATx( provider, account, @@ -123,11 +127,55 @@ describe('Clave Contracts - Validator tests', () => { }); describe('Invalid tx and signature', () => { - it('should revert sending the tx', async () => {}); + it('should revert sending the tx', async () => { + const mockTxData = txData; + mockTxData.to = await Wallet.createRandom().getAddress(); // corrupted tx data + + const tx = await prepareEOATx( + provider, + account, + txData, + await newK1Validator.getAddress(), + newK1Owner, + ); + + try { + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + await txReceipt.wait(); + assert(false); + } catch (err) {} + + const richBalanceAfter = await provider.getBalance( + richAddress, + ); + expect(richBalanceAfter).to.eq(richBalanceBefore); + }); }); describe('Tx and wrong signature', () => { - it('should revert sending the tx', async () => {}); + it('should revert sending the tx', async () => { + const tx = await prepareMockTx( + provider, + account, + txData, + await newK1Validator.getAddress(), + ); + + try { + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + await txReceipt.wait(); + assert(false); + } catch (err) {} + + const richBalanceAfter = await provider.getBalance( + richAddress, + ); + expect(richBalanceAfter).to.eq(richBalanceBefore); + }); }); }); }); From 8d96964026a0b300b96e6339f4964f5b306bfdfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Wed, 18 Sep 2024 14:53:33 +0300 Subject: [PATCH 077/103] [clave-contracts] feat: add passkeyvalidator tests mock --- .../validators/passkeyvalidator.test.ts | 149 ++++++++++++++++++ test/utils/transactions.ts | 12 ++ 2 files changed, 161 insertions(+) create mode 100644 test/accounts/validators/passkeyvalidator.test.ts diff --git a/test/accounts/validators/passkeyvalidator.test.ts b/test/accounts/validators/passkeyvalidator.test.ts new file mode 100644 index 0000000..7aeb0fa --- /dev/null +++ b/test/accounts/validators/passkeyvalidator.test.ts @@ -0,0 +1,149 @@ +/** + * Copyright Clave - All Rights Reserved + * Unauthorized copying of this file, via any medium is strictly prohibited + * Proprietary and confidential + */ +import { assert, expect } from 'chai'; +import type { ec } from 'elliptic'; +import { parseEther } from 'ethers'; +import * as hre from 'hardhat'; +import { Provider, Wallet, utils } from 'zksync-ethers'; +import type { Contract } from 'zksync-ethers'; +import type { TransactionLike } from 'zksync-ethers/build/types'; + +import { LOCAL_RICH_WALLETS, getWallet } from '../../../deploy/utils'; +import { ClaveDeployer } from '../../utils/deployer'; +import { fixture } from '../../utils/fixture'; +import { VALIDATORS } from '../../utils/names'; +import { + ethTransfer, + prepareMockTx, + preparePasskeyTx, +} from '../../utils/transactions'; + +describe('Clave Contracts - Validator tests', () => { + let deployer: ClaveDeployer; + let provider: Provider; + let richWallet: Wallet; + let passkeyValidator: Contract; + let account: Contract; + let keyPair: ec.KeyPair; + + before(async () => { + richWallet = getWallet(hre, LOCAL_RICH_WALLETS[0].privateKey); + deployer = new ClaveDeployer(hre, richWallet); + provider = new Provider(hre.network.config.url, undefined, { + cacheTimeout: -1, + }); + + [, , , , passkeyValidator, account, keyPair] = await fixture( + deployer, + VALIDATORS.PASSKEY, + ); + + const accountAddress = await account.getAddress(); + + await deployer.fund(10000, accountAddress); + }); + + describe('PasskeyValidator', () => { + it('should check existing validator', async () => { + const validatorAddress = await passkeyValidator.getAddress(); + + expect(await account.r1IsValidator(validatorAddress)).to.be.true; + }); + + describe('Signature checks', () => { + let richAddress: string; + let richBalanceBefore: bigint; + let amount: bigint; + + let txData: TransactionLike; + + beforeEach(async () => { + richBalanceBefore = await provider.getBalance(richAddress); + }); + + before(async () => { + richAddress = await richWallet.getAddress(); + + amount = parseEther('1'); + + txData = ethTransfer(richAddress, amount); + }); + + describe('Valid tx and signature', () => { + it('should send a tx', async () => { + const tx = await preparePasskeyTx( + provider, + account, + txData, + await passkeyValidator.getAddress(), + keyPair, + ); + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + await txReceipt.wait(); + + const richBalanceAfter = await provider.getBalance( + richAddress, + ); + expect(richBalanceAfter).to.eq(richBalanceBefore + amount); + }); + }); + + describe('Invalid tx and signature', () => { + it('should revert sending the tx', async () => { + const mockTxData = txData; + mockTxData.to = await Wallet.createRandom().getAddress(); // corrupted tx data + + const tx = await preparePasskeyTx( + provider, + account, + txData, + await passkeyValidator.getAddress(), + keyPair, + ); + + try { + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + await txReceipt.wait(); + assert(false); + } catch (err) {} + + const richBalanceAfter = await provider.getBalance( + richAddress, + ); + expect(richBalanceAfter).to.eq(richBalanceBefore); + }); + }); + + describe('Tx and wrong signature', () => { + it('should revert sending the tx', async () => { + const tx = await prepareMockTx( + provider, + account, + txData, + await passkeyValidator.getAddress(), + ); + + try { + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + await txReceipt.wait(); + assert(false); + } catch (err) {} + + const richBalanceAfter = await provider.getBalance( + richAddress, + ); + expect(richBalanceAfter).to.eq(richBalanceBefore); + }); + }); + }); + }); +}); diff --git a/test/utils/transactions.ts b/test/utils/transactions.ts index 8b4373a..fdbdc54 100644 --- a/test/utils/transactions.ts +++ b/test/utils/transactions.ts @@ -259,3 +259,15 @@ export async function prepareEOATx( return tx; } + +export async function preparePasskeyTx( + provider: Provider, + account: Contract, + tx: types.TransactionLike, + validatorAddress: string, + keyPair: ec.KeyPair, + hookData: Array = [], + paymasterParams?: types.PaymasterParams, +): Promise { + throw new Error('Not implemented'); +} From e3a33207a1583cb984708a5b760315f98fc0d27b Mon Sep 17 00:00:00 2001 From: tahos81 Date: Fri, 20 Sep 2024 15:13:06 +0300 Subject: [PATCH 078/103] Create buffer.ts --- test/utils/buffer.ts | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 test/utils/buffer.ts diff --git a/test/utils/buffer.ts b/test/utils/buffer.ts new file mode 100644 index 0000000..8afae9a --- /dev/null +++ b/test/utils/buffer.ts @@ -0,0 +1,35 @@ +export function bufferFromBase64url(base64url: string): Buffer { + return Buffer.from(toBase64(base64url), 'base64'); +} + +export function bufferFromString(string: string): Buffer { + return Buffer.from(string, 'utf8'); +} + +function toBase64(base64url: string | Buffer): string { + base64url = base64url.toString(); + return padString(base64url).replace(/\-/g, '+').replace(/_/g, '/'); +} + +function padString(input: string): string { + const segmentLength = 4; + const stringLength = input.length; + const diff = stringLength % segmentLength; + + if (!diff) { + return input; + } + + let position = stringLength; + let padLength = segmentLength - diff; + const paddedStringLength = stringLength + padLength; + const buffer = Buffer.alloc(paddedStringLength); + + buffer.write(input); + + while (padLength--) { + buffer.write('=', position++); + } + + return buffer.toString(); +} From 1dfb10a14129b914524979f9f4579d7f7fcdab56 Mon Sep 17 00:00:00 2001 From: tahos81 Date: Fri, 20 Sep 2024 15:13:09 +0300 Subject: [PATCH 079/103] Create passkey.ts --- test/utils/passkey.ts | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 test/utils/passkey.ts diff --git a/test/utils/passkey.ts b/test/utils/passkey.ts new file mode 100644 index 0000000..b068819 --- /dev/null +++ b/test/utils/passkey.ts @@ -0,0 +1,31 @@ +/** + * Copyright Clave - All Rights Reserved + * Unauthorized copying of this file, via any medium is strictly prohibited + * Proprietary and confidential + */ +import { sha256 } from 'ethers'; + +import { bufferFromBase64url, bufferFromString } from './buffer'; + +// hash of 'https://getclave.io' + (BE, BS, UP, UV) flags set + unincremented sign counter +const authData = bufferFromBase64url( + 'F1-vhQTCzdfAF3iosO_Uh07LOu_X67cHmpQfe-iJfUEdAAAAAA', +); +const clientDataPrefix = bufferFromString( + '{"type":"webauthn.get","challenge":"', +); +const clientDataSuffix = bufferFromString('","origin":"https://getclave.io"}'); + +export function getSignedData(challenge: string): string { + const challengeBuffer = Buffer.from(challenge.slice(2), 'hex'); + const challengeBase64 = challengeBuffer.toString('base64url'); + const clientData = Buffer.concat([ + clientDataPrefix, + bufferFromString(challengeBase64), + clientDataSuffix, + ]); + + const clientDataHash = Buffer.from(sha256(clientData).slice(2), 'hex'); + + return sha256(Buffer.concat([authData, clientDataHash])); +} From 3e8d28c45731fffd298c5b5768ee425f13d00f0b Mon Sep 17 00:00:00 2001 From: tahos81 Date: Fri, 20 Sep 2024 15:13:12 +0300 Subject: [PATCH 080/103] Update transactions.ts --- test/utils/transactions.ts | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/test/utils/transactions.ts b/test/utils/transactions.ts index fdbdc54..880657e 100644 --- a/test/utils/transactions.ts +++ b/test/utils/transactions.ts @@ -11,6 +11,7 @@ import { EIP712Signer, utils } from 'zksync-ethers'; import type { CallStruct } from '../../typechain-types/contracts/batch/BatchCaller'; import { sign } from './p256'; +import { getSignedData } from './passkey'; export const ethTransfer = ( to: string, @@ -269,5 +270,39 @@ export async function preparePasskeyTx( hookData: Array = [], paymasterParams?: types.PaymasterParams, ): Promise { - throw new Error('Not implemented'); + if (tx.value == undefined) { + tx.value = parseEther('0'); + } + + tx = { + ...tx, + from: await account.getAddress(), + nonce: await provider.getTransactionCount(await account.getAddress()), + gasLimit: 30_000_000, + gasPrice: await provider.getGasPrice(), + chainId: (await provider.getNetwork()).chainId, + type: 113, + customData: { + gasPerPubdata: utils.DEFAULT_GAS_PER_PUBDATA_LIMIT, + paymasterParams: paymasterParams, + } as types.Eip712Meta, + }; + + const signedTxHash = EIP712Signer.getSignedDigest(tx); + + const abiCoder = ethers.AbiCoder.defaultAbiCoder(); + let signature = sign(getSignedData(signedTxHash.toString()), keyPair); + signature = '0x01' + signature.slice(2); + + signature = abiCoder.encode( + ['bytes', 'address', 'bytes[]'], + [signature, validatorAddress, hookData], + ); + + tx.customData = { + ...tx.customData, + customSignature: signature, + }; + + return tx; } From a958ae29cc8715966531ea3266097170eec56306 Mon Sep 17 00:00:00 2001 From: tahos81 Date: Fri, 20 Sep 2024 15:13:55 +0300 Subject: [PATCH 081/103] husky --- test/utils/buffer.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/utils/buffer.ts b/test/utils/buffer.ts index 8afae9a..1473a94 100644 --- a/test/utils/buffer.ts +++ b/test/utils/buffer.ts @@ -1,3 +1,8 @@ +/** + * Copyright Clave - All Rights Reserved + * Unauthorized copying of this file, via any medium is strictly prohibited + * Proprietary and confidential + */ export function bufferFromBase64url(base64url: string): Buffer { return Buffer.from(toBase64(base64url), 'base64'); } From 5bad7c9821e768276e81b2436b19b12002d868bd Mon Sep 17 00:00:00 2001 From: tahos81 Date: Fri, 20 Sep 2024 16:56:43 +0300 Subject: [PATCH 082/103] malleability fix --- test/utils/transactions.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/utils/transactions.ts b/test/utils/transactions.ts index 880657e..eefd5c7 100644 --- a/test/utils/transactions.ts +++ b/test/utils/transactions.ts @@ -292,6 +292,25 @@ export async function preparePasskeyTx( const abiCoder = ethers.AbiCoder.defaultAbiCoder(); let signature = sign(getSignedData(signedTxHash.toString()), keyPair); + // Perform malleability check and invert 's' if it's too large + const rs = signature.slice(2); + const r = '0x' + rs.slice(0, 64); + let s = BigInt('0x' + rs.slice(64, 128)); + + // Maximum allowed value for 's' in secp256r1 + const lowSmax = BigInt( + '0x7fffffff800000007fffffffffffffffde737d56d38bcf4279dce5617e3192a8', + ); + + if (s > lowSmax) { + // If 's' is too large, invert it + s = + BigInt( + '0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551', + ) - s; + // Reconstruct the signature with inverted 's' + signature = r + s.toString(16).padStart(64, '0'); + } signature = '0x01' + signature.slice(2); signature = abiCoder.encode( From 029dc7c2efe9d991dd6faf196345122905aab695 Mon Sep 17 00:00:00 2001 From: tahos81 Date: Fri, 20 Sep 2024 16:58:10 +0300 Subject: [PATCH 083/103] fix malleability --- test/utils/transactions.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/utils/transactions.ts b/test/utils/transactions.ts index 880657e..28eccd3 100644 --- a/test/utils/transactions.ts +++ b/test/utils/transactions.ts @@ -294,6 +294,26 @@ export async function preparePasskeyTx( let signature = sign(getSignedData(signedTxHash.toString()), keyPair); signature = '0x01' + signature.slice(2); + // Perform malleability check and invert 's' if it's too large + const rs = signature.slice(2); + const r = '0x' + rs.slice(0, 64); + let s = BigInt('0x' + rs.slice(64, 128)); + + // Maximum allowed value for 's' in secp256r1 + const lowSmax = BigInt( + '0x7fffffff800000007fffffffffffffffde737d56d38bcf4279dce5617e3192a8', + ); + + if (s > lowSmax) { + // If 's' is too large, invert it + s = + BigInt( + '0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551', + ) - s; + // Reconstruct the signature with inverted 's' + signature = r + s.toString(16).padStart(64, '0'); + } + signature = abiCoder.encode( ['bytes', 'address', 'bytes[]'], [signature, validatorAddress, hookData], From 84ef886292d19b5d7d74d94413a512b66dce80a2 Mon Sep 17 00:00:00 2001 From: tahos81 Date: Fri, 20 Sep 2024 17:20:25 +0300 Subject: [PATCH 084/103] Revert "fix malleability" This reverts commit c7649664e34effa0c9e11b9ab6eaf2e4430e8a17. --- test/utils/transactions.ts | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/test/utils/transactions.ts b/test/utils/transactions.ts index 240de31..eefd5c7 100644 --- a/test/utils/transactions.ts +++ b/test/utils/transactions.ts @@ -313,26 +313,6 @@ export async function preparePasskeyTx( } signature = '0x01' + signature.slice(2); - // Perform malleability check and invert 's' if it's too large - const rs = signature.slice(2); - const r = '0x' + rs.slice(0, 64); - let s = BigInt('0x' + rs.slice(64, 128)); - - // Maximum allowed value for 's' in secp256r1 - const lowSmax = BigInt( - '0x7fffffff800000007fffffffffffffffde737d56d38bcf4279dce5617e3192a8', - ); - - if (s > lowSmax) { - // If 's' is too large, invert it - s = - BigInt( - '0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551', - ) - s; - // Reconstruct the signature with inverted 's' - signature = r + s.toString(16).padStart(64, '0'); - } - signature = abiCoder.encode( ['bytes', 'address', 'bytes[]'], [signature, validatorAddress, hookData], From f55718ae3936ea23064102d6907ac6a613721ff5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Mon, 23 Sep 2024 19:33:06 +0300 Subject: [PATCH 085/103] [clave-contracts] feat: initial cloud recovery test - incomplete --- test/accounts/modules/cloudrecovery.test.ts | 137 ++++++++++++++++++++ test/utils/recovery/recovery.ts | 96 ++++++++++++++ 2 files changed, 233 insertions(+) create mode 100644 test/accounts/modules/cloudrecovery.test.ts create mode 100644 test/utils/recovery/recovery.ts diff --git a/test/accounts/modules/cloudrecovery.test.ts b/test/accounts/modules/cloudrecovery.test.ts new file mode 100644 index 0000000..aa7d60d --- /dev/null +++ b/test/accounts/modules/cloudrecovery.test.ts @@ -0,0 +1,137 @@ +/** + * Copyright Clave - All Rights Reserved + * Unauthorized copying of this file, via any medium is strictly prohibited + * Proprietary and confidential + */ +import { expect } from 'chai'; +import type { ec } from 'elliptic'; +import { AbiCoder } from 'ethers'; +import * as hre from 'hardhat'; +import type { Contract } from 'zksync-ethers'; +import { Provider, Wallet } from 'zksync-ethers'; + +import { LOCAL_RICH_WALLETS, getWallet } from '../../../deploy/utils'; +import { ClaveDeployer } from '../../utils/deployer'; +import { fixture } from '../../utils/fixture'; +import { addModule } from '../../utils/managers/modulemanager'; +import { VALIDATORS } from '../../utils/names'; +import { encodePublicKey, genKey } from '../../utils/p256'; +import { startRecovery } from '../../utils/recovery/recovery'; + +describe('Clave Contracts - Manager tests', () => { + let deployer: ClaveDeployer; + let provider: Provider; + let richWallet: Wallet; + let teeValidator: Contract; + let account: Contract; + let keyPair: ec.KeyPair; + + before(async () => { + richWallet = getWallet(hre, LOCAL_RICH_WALLETS[0].privateKey); + deployer = new ClaveDeployer(hre, richWallet); + provider = new Provider(hre.network.config.url, undefined, { + cacheTimeout: -1, + }); + + [, , , , teeValidator, account, keyPair] = await fixture( + deployer, + VALIDATORS.TEE, + ); + + const accountAddress = await account.getAddress(); + + await deployer.fund(10000, accountAddress); + }); + + describe('Module Tests - Cloud Recovery Module', () => { + let cloudRecoveryModule: Contract; + let cloudGuardian: Wallet; + let newKeyPair: ec.KeyPair; + + describe('Adding & Initializing module', () => { + before(async () => { + cloudGuardian = new Wallet( + Wallet.createRandom().privateKey, + provider, + ); + + newKeyPair = genKey(); + }); + + it('should check existing modules', async () => { + expect(await account.listModules()).to.deep.eq([]); + }); + + it('should add a new module', async () => { + cloudRecoveryModule = await deployer.deployCustomContract( + 'CloudRecoveryModule', + ['TEST', '0', 0], + ); + expect( + await account.isModule( + await cloudRecoveryModule.getAddress(), + ), + ).to.be.false; + + const initData = AbiCoder.defaultAbiCoder().encode( + ['address'], + [await cloudGuardian.getAddress()], + ); + await addModule( + provider, + account, + teeValidator, + cloudRecoveryModule, + initData, + keyPair, + ); + expect( + await account.isModule( + await cloudRecoveryModule.getAddress(), + ), + ).to.be.true; + + const expectedModules = [ + await cloudRecoveryModule.getAddress(), + ]; + expect(await account.listModules()).to.deep.eq(expectedModules); + }); + + it('should init the module successfully', async () => { + const status = await cloudRecoveryModule.isInited( + await account.getAddress(), + ); + expect(status).to.eq(true); + }); + + it('should assign the guardian correctly', async () => { + const guardian = await cloudRecoveryModule.getGuardian( + await account.getAddress(), + ); + expect(guardian).to.eq(await cloudGuardian.getAddress()); + }); + + it('should start recovery process', async () => { + expect( + await cloudRecoveryModule.isRecovering( + await account.getAddress(), + ), + ).to.be.false; + + await startRecovery( + cloudGuardian, + account, + cloudRecoveryModule, + teeValidator, + encodePublicKey(newKeyPair), + ); + + expect( + await cloudRecoveryModule.isRecovering( + await account.getAddress(), + ), + ).to.be.true; + }); + }); + }); +}); diff --git a/test/utils/recovery/recovery.ts b/test/utils/recovery/recovery.ts new file mode 100644 index 0000000..44a06fd --- /dev/null +++ b/test/utils/recovery/recovery.ts @@ -0,0 +1,96 @@ +/** + * Copyright Clave - All Rights Reserved + * Unauthorized copying of this file, via any medium is strictly prohibited + * Proprietary and confidential + */ +import { AbiCoder, TypedDataEncoder } from 'ethers'; +import { type Contract, type Wallet } from 'zksync-ethers'; + +type RecoveryEIP712DomainType = { + name: string; + version: string; + chainId: number; + verifyingContract: string; +}; + +type StartRecoveryParams = { + recoveringAddress: string; + newOwner: string; + nonce: number; +}; + +async function signRecoveryEIP712Hash( + params: StartRecoveryParams, + recoveryContract: Contract, + validator: Contract, + wallet: Wallet, +): Promise { + const eip712Hash = await recoveryContract.getEip712Hash(params); + + const { chainId } = await wallet.provider.getNetwork(); + const numberChainId = Number(chainId); + + const eip1271Hash = TypedDataEncoder.hash( + recoveryEIP712Domain( + 'TEST', + '0', + numberChainId, + await recoveryContract.getAddress(), + ), + { ClaveMessage: [{ name: 'signedHash', type: 'bytes32' }] }, + { + signedHash: eip712Hash, + }, + ); + + const signature = wallet.signingKey.sign(eip1271Hash).serialized; + + const abiCoder = AbiCoder.defaultAbiCoder(); + const encodedSignature = abiCoder.encode( + ['bytes', 'address'], + [signature, await validator.getAddress()], + ); + + return encodedSignature; +} + +export async function startRecovery( + cloudGuardian: Wallet, + account: Contract, + module: Contract, + validator: Contract, + newOwner: string, +): Promise { + const recoveringAddress = await account.getAddress(); + const recoveryNonce = await module.recoveryNonces(recoveringAddress); + + const params: StartRecoveryParams = { + recoveringAddress: recoveringAddress, + newOwner, + nonce: recoveryNonce, + }; + + const signature = await signRecoveryEIP712Hash( + params, + module, + validator, + cloudGuardian, + ); + + const startRecoveryTx = await module.startRecovery(params, signature); + await startRecoveryTx.wait(); +} + +const recoveryEIP712Domain = ( + name: string, + version: string, + chainId: number, + verifyingContract: string, +): RecoveryEIP712DomainType => { + return { + name: name, + version: version, + chainId: chainId, + verifyingContract: verifyingContract, + }; +}; From 5fcdc618ffe9bb15fcf09ede1bfa4f68f5ddfb37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Tue, 24 Sep 2024 17:47:41 +0300 Subject: [PATCH 086/103] [clave-contracts] fix: eip712 domains --- test/utils/recovery/recovery.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/utils/recovery/recovery.ts b/test/utils/recovery/recovery.ts index 44a06fd..9a10dfc 100644 --- a/test/utils/recovery/recovery.ts +++ b/test/utils/recovery/recovery.ts @@ -32,10 +32,10 @@ async function signRecoveryEIP712Hash( const eip1271Hash = TypedDataEncoder.hash( recoveryEIP712Domain( - 'TEST', - '0', + 'Clave1271', + '1.0.0', numberChainId, - await recoveryContract.getAddress(), + params.recoveringAddress, ), { ClaveMessage: [{ name: 'signedHash', type: 'bytes32' }] }, { From 51150f0027708434dd35a2f90b7b8b428a2d3c51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Tue, 24 Sep 2024 18:05:20 +0300 Subject: [PATCH 087/103] [clave-contracts] fix: addresses --- test/utils/recovery/recovery.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/utils/recovery/recovery.ts b/test/utils/recovery/recovery.ts index 9a10dfc..07ae1a2 100644 --- a/test/utils/recovery/recovery.ts +++ b/test/utils/recovery/recovery.ts @@ -20,6 +20,7 @@ type StartRecoveryParams = { }; async function signRecoveryEIP712Hash( + account: Contract, params: StartRecoveryParams, recoveryContract: Contract, validator: Contract, @@ -35,7 +36,7 @@ async function signRecoveryEIP712Hash( 'Clave1271', '1.0.0', numberChainId, - params.recoveringAddress, + await account.getAddress(), ), { ClaveMessage: [{ name: 'signedHash', type: 'bytes32' }] }, { @@ -61,7 +62,7 @@ export async function startRecovery( validator: Contract, newOwner: string, ): Promise { - const recoveringAddress = await account.getAddress(); + const recoveringAddress = await module.getAddress(); const recoveryNonce = await module.recoveryNonces(recoveringAddress); const params: StartRecoveryParams = { @@ -71,6 +72,7 @@ export async function startRecovery( }; const signature = await signRecoveryEIP712Hash( + account, params, module, validator, From bc3e74f7d2e6116dade7770121cbab1153c1e4c3 Mon Sep 17 00:00:00 2001 From: tahos81 Date: Mon, 30 Sep 2024 16:06:19 +0300 Subject: [PATCH 088/103] fix: start recovery helper --- test/utils/recovery/recovery.ts | 52 ++------------------------------- 1 file changed, 3 insertions(+), 49 deletions(-) diff --git a/test/utils/recovery/recovery.ts b/test/utils/recovery/recovery.ts index 07ae1a2..1fea1e8 100644 --- a/test/utils/recovery/recovery.ts +++ b/test/utils/recovery/recovery.ts @@ -3,16 +3,8 @@ * Unauthorized copying of this file, via any medium is strictly prohibited * Proprietary and confidential */ -import { AbiCoder, TypedDataEncoder } from 'ethers'; import { type Contract, type Wallet } from 'zksync-ethers'; -type RecoveryEIP712DomainType = { - name: string; - version: string; - chainId: number; - verifyingContract: string; -}; - type StartRecoveryParams = { recoveringAddress: string; newOwner: string; @@ -27,32 +19,8 @@ async function signRecoveryEIP712Hash( wallet: Wallet, ): Promise { const eip712Hash = await recoveryContract.getEip712Hash(params); - - const { chainId } = await wallet.provider.getNetwork(); - const numberChainId = Number(chainId); - - const eip1271Hash = TypedDataEncoder.hash( - recoveryEIP712Domain( - 'Clave1271', - '1.0.0', - numberChainId, - await account.getAddress(), - ), - { ClaveMessage: [{ name: 'signedHash', type: 'bytes32' }] }, - { - signedHash: eip712Hash, - }, - ); - - const signature = wallet.signingKey.sign(eip1271Hash).serialized; - - const abiCoder = AbiCoder.defaultAbiCoder(); - const encodedSignature = abiCoder.encode( - ['bytes', 'address'], - [signature, await validator.getAddress()], - ); - - return encodedSignature; + const signature = wallet.signingKey.sign(eip712Hash).serialized; + return signature; } export async function startRecovery( @@ -62,7 +30,7 @@ export async function startRecovery( validator: Contract, newOwner: string, ): Promise { - const recoveringAddress = await module.getAddress(); + const recoveringAddress = await account.getAddress(); const recoveryNonce = await module.recoveryNonces(recoveringAddress); const params: StartRecoveryParams = { @@ -82,17 +50,3 @@ export async function startRecovery( const startRecoveryTx = await module.startRecovery(params, signature); await startRecoveryTx.wait(); } - -const recoveryEIP712Domain = ( - name: string, - version: string, - chainId: number, - verifyingContract: string, -): RecoveryEIP712DomainType => { - return { - name: name, - version: version, - chainId: chainId, - verifyingContract: verifyingContract, - }; -}; From b0b68055599960817703a693fb3e7c313bfed8d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Tue, 1 Oct 2024 15:14:40 +0300 Subject: [PATCH 089/103] [clave-contracts] feat: more cloud recovery test cases --- test/accounts/modules/cloudrecovery.test.ts | 116 +++++++++++++++++++- test/utils/recovery/recovery.ts | 66 ++++++++++- 2 files changed, 175 insertions(+), 7 deletions(-) diff --git a/test/accounts/modules/cloudrecovery.test.ts b/test/accounts/modules/cloudrecovery.test.ts index aa7d60d..b2bd5c2 100644 --- a/test/accounts/modules/cloudrecovery.test.ts +++ b/test/accounts/modules/cloudrecovery.test.ts @@ -3,7 +3,7 @@ * Unauthorized copying of this file, via any medium is strictly prohibited * Proprietary and confidential */ -import { expect } from 'chai'; +import { assert, expect } from 'chai'; import type { ec } from 'elliptic'; import { AbiCoder } from 'ethers'; import * as hre from 'hardhat'; @@ -16,7 +16,12 @@ import { fixture } from '../../utils/fixture'; import { addModule } from '../../utils/managers/modulemanager'; import { VALIDATORS } from '../../utils/names'; import { encodePublicKey, genKey } from '../../utils/p256'; -import { startRecovery } from '../../utils/recovery/recovery'; +import { + executeRecovery, + startRecovery, + stopRecovery, + updateCloudGuardian, +} from '../../utils/recovery/recovery'; describe('Clave Contracts - Manager tests', () => { let deployer: ClaveDeployer; @@ -110,7 +115,9 @@ describe('Clave Contracts - Manager tests', () => { ); expect(guardian).to.eq(await cloudGuardian.getAddress()); }); + }); + describe('Recovery process', () => { it('should start recovery process', async () => { expect( await cloudRecoveryModule.isRecovering( @@ -132,6 +139,111 @@ describe('Clave Contracts - Manager tests', () => { ), ).to.be.true; }); + + it('should execute recovery process', async () => { + expect( + await cloudRecoveryModule.isRecovering( + await account.getAddress(), + ), + ).to.be.true; + + await executeRecovery(account, cloudRecoveryModule); + + expect( + await cloudRecoveryModule.isRecovering( + await account.getAddress(), + ), + ).to.be.false; + }); + }); + + describe('Alternative recovery process scenarios', () => { + let newNewKeyPair: ec.KeyPair; + + before(async () => { + newNewKeyPair = genKey(); + }); + + it('shoud revert changing the guardian if recovery is in progress', async () => { + expect( + await cloudRecoveryModule.isRecovering( + await account.getAddress(), + ), + ).to.be.false; + + await startRecovery( + cloudGuardian, + account, + cloudRecoveryModule, + teeValidator, + encodePublicKey(newNewKeyPair), + ); + + expect( + await cloudRecoveryModule.isRecovering( + await account.getAddress(), + ), + ).to.be.true; + + expect( + await cloudRecoveryModule.isRecovering( + await account.getAddress(), + ), + ).to.be.true; + const guardian = await cloudRecoveryModule.getGuardian( + await account.getAddress(), + ); + expect(guardian).to.eq(await cloudGuardian.getAddress()); + + const newGuardianAddress = new Wallet( + Wallet.createRandom().privateKey, + provider, + ); + + try { + await updateCloudGuardian( + provider, + account, + cloudRecoveryModule, + teeValidator, + await newGuardianAddress.getAddress(), + keyPair, + ); + assert(false, 'Should revert'); + } catch (err) {} + + expect( + await cloudRecoveryModule.isRecovering( + await account.getAddress(), + ), + ).to.be.true; + const guardianLater = await cloudRecoveryModule.getGuardian( + await account.getAddress(), + ); + expect(guardian).to.eq(guardianLater); + }); + + it('should stop the recovery process', async () => { + expect( + await cloudRecoveryModule.isRecovering( + await account.getAddress(), + ), + ).to.be.true; + + await stopRecovery( + provider, + account, + cloudRecoveryModule, + teeValidator, + newKeyPair, + ); + + expect( + await cloudRecoveryModule.isRecovering( + await account.getAddress(), + ), + ).to.be.false; + }); }); }); }); diff --git a/test/utils/recovery/recovery.ts b/test/utils/recovery/recovery.ts index 1fea1e8..ef7fa12 100644 --- a/test/utils/recovery/recovery.ts +++ b/test/utils/recovery/recovery.ts @@ -3,7 +3,11 @@ * Unauthorized copying of this file, via any medium is strictly prohibited * Proprietary and confidential */ -import { type Contract, type Wallet } from 'zksync-ethers'; +import type { ec } from 'elliptic'; +import type { Contract, Provider, Wallet } from 'zksync-ethers'; +import { utils } from 'zksync-ethers'; + +import { prepareTeeTx } from '../transactions'; type StartRecoveryParams = { recoveringAddress: string; @@ -12,10 +16,8 @@ type StartRecoveryParams = { }; async function signRecoveryEIP712Hash( - account: Contract, params: StartRecoveryParams, recoveryContract: Contract, - validator: Contract, wallet: Wallet, ): Promise { const eip712Hash = await recoveryContract.getEip712Hash(params); @@ -40,13 +42,67 @@ export async function startRecovery( }; const signature = await signRecoveryEIP712Hash( - account, params, module, - validator, cloudGuardian, ); const startRecoveryTx = await module.startRecovery(params, signature); await startRecoveryTx.wait(); } + +export async function stopRecovery( + provider: Provider, + account: Contract, + module: Contract, + validator: Contract, + keyPair: ec.KeyPair, +): Promise { + const stopRecoveryTx = await module.stopRecovery.populateTransaction(); + + const tx = await prepareTeeTx( + provider, + account, + stopRecoveryTx, + await validator.getAddress(), + keyPair, + ); + + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + await txReceipt.wait(); +} + +export async function updateCloudGuardian( + provider: Provider, + account: Contract, + module: Contract, + validator: Contract, + newAddress: string, + keyPair: ec.KeyPair, +): Promise { + const updateGuardianTx = await module.updateGuardian.populateTransaction( + newAddress, + ); + const tx = await prepareTeeTx( + provider, + account, + updateGuardianTx, + await validator.getAddress(), + keyPair, + ); + + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + await txReceipt.wait(); +} + +export async function executeRecovery( + account: Contract, + module: Contract, +): Promise { + const tx = await module.executeRecovery(await account.getAddress()); + await tx.wait(); +} From 780b1bb7d8cf4e003ca8eaf3f3311267908656a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Tue, 1 Oct 2024 17:51:09 +0300 Subject: [PATCH 090/103] [clave-contracts] refactor: change deployment order --- test/accounts/modules/cloudrecovery.test.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/test/accounts/modules/cloudrecovery.test.ts b/test/accounts/modules/cloudrecovery.test.ts index b2bd5c2..382ec31 100644 --- a/test/accounts/modules/cloudrecovery.test.ts +++ b/test/accounts/modules/cloudrecovery.test.ts @@ -31,6 +31,8 @@ describe('Clave Contracts - Manager tests', () => { let account: Contract; let keyPair: ec.KeyPair; + let cloudRecoveryModule: Contract; + before(async () => { richWallet = getWallet(hre, LOCAL_RICH_WALLETS[0].privateKey); deployer = new ClaveDeployer(hre, richWallet); @@ -46,10 +48,14 @@ describe('Clave Contracts - Manager tests', () => { const accountAddress = await account.getAddress(); await deployer.fund(10000, accountAddress); + + cloudRecoveryModule = await deployer.deployCustomContract( + 'CloudRecoveryModule', + ['TEST', '0', 0], + ); }); describe('Module Tests - Cloud Recovery Module', () => { - let cloudRecoveryModule: Contract; let cloudGuardian: Wallet; let newKeyPair: ec.KeyPair; @@ -68,10 +74,6 @@ describe('Clave Contracts - Manager tests', () => { }); it('should add a new module', async () => { - cloudRecoveryModule = await deployer.deployCustomContract( - 'CloudRecoveryModule', - ['TEST', '0', 0], - ); expect( await account.isModule( await cloudRecoveryModule.getAddress(), From 78a868e62a7f0f287d4ee6df67df614cad2d3025 Mon Sep 17 00:00:00 2001 From: tahos81 Date: Wed, 2 Oct 2024 14:20:20 +0300 Subject: [PATCH 091/103] venus ABI's --- subgraph/abis/VenusReward.json | 39 +++++++++++++++++++++ subgraph/abis/VenusToken.json | 64 ++++++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 subgraph/abis/VenusReward.json create mode 100644 subgraph/abis/VenusToken.json diff --git a/subgraph/abis/VenusReward.json b/subgraph/abis/VenusReward.json new file mode 100644 index 0000000..93cd28f --- /dev/null +++ b/subgraph/abis/VenusReward.json @@ -0,0 +1,39 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vToken", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "supplier", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "rewardTokenDelta", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "rewardTokenTotal", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "rewardTokenSupplyIndex", + "type": "uint256" + } + ], + "name": "DistributedSupplierRewardToken", + "type": "event" + } +] diff --git a/subgraph/abis/VenusToken.json b/subgraph/abis/VenusToken.json new file mode 100644 index 0000000..146a39c --- /dev/null +++ b/subgraph/abis/VenusToken.json @@ -0,0 +1,64 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "minter", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "mintAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "mintTokens", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "accountBalance", + "type": "uint256" + } + ], + "name": "Mint", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "redeemer", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "redeemAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "redeemTokens", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "accountBalance", + "type": "uint256" + } + ], + "name": "Redeem", + "type": "event" + } +] From d396897b427509ba69d9147f0b2953667146b4e0 Mon Sep 17 00:00:00 2001 From: tahos81 Date: Wed, 2 Oct 2024 14:20:26 +0300 Subject: [PATCH 092/103] new protocol type --- subgraph/schema.graphql | 1 + 1 file changed, 1 insertion(+) diff --git a/subgraph/schema.graphql b/subgraph/schema.graphql index 3340cee..db6f378 100644 --- a/subgraph/schema.graphql +++ b/subgraph/schema.graphql @@ -50,6 +50,7 @@ enum EarnProtocol { ZeroLend Clave Meow + Venus } type EarnPosition @entity { From 18b6365f0aec9d57a9f0a87967ce0aab3897ba62 Mon Sep 17 00:00:00 2001 From: tahos81 Date: Wed, 2 Oct 2024 14:20:38 +0300 Subject: [PATCH 093/103] venus mapping files --- subgraph/src/venus-pool-usdce.ts | 110 +++++++++++++++++++++++++++++++ subgraph/src/venus-pool-usdt.ts | 110 +++++++++++++++++++++++++++++++ subgraph/src/venus-reward.ts | 55 ++++++++++++++++ 3 files changed, 275 insertions(+) create mode 100644 subgraph/src/venus-pool-usdce.ts create mode 100644 subgraph/src/venus-pool-usdt.ts create mode 100644 subgraph/src/venus-reward.ts diff --git a/subgraph/src/venus-pool-usdce.ts b/subgraph/src/venus-pool-usdce.ts new file mode 100644 index 0000000..da66087 --- /dev/null +++ b/subgraph/src/venus-pool-usdce.ts @@ -0,0 +1,110 @@ +/* eslint-disable @typescript-eslint/consistent-type-imports */ +import { Address } from '@graphprotocol/graph-ts'; + +import { + Mint as MintEvent, + Redeem as RedeemEvent, +} from '../generated/VenusUsdce/VenusToken'; +import { ClaveAccount } from '../generated/schema'; +import { + ZERO, + getOrCreateDailyEarnFlow, + getOrCreateDay, + getOrCreateEarnPosition, + getOrCreateMonth, + getOrCreateMonthlyEarnFlow, + getOrCreateWeek, + getOrCreateWeeklyEarnFlow, +} from './helpers'; + +const protocol = 'Venus'; + +const token = Address.fromHexString( + '0x3355df6D4c9C3035724Fd0e3914dE96A5a83aaf4', +); + +export function handleMint(event: MintEvent): void { + const account = ClaveAccount.load(event.params.minter); + if (!account) { + return; + } + + const pool = event.address; + const amount = event.params.mintAmount; + + const day = getOrCreateDay(event.block.timestamp); + const week = getOrCreateWeek(event.block.timestamp); + const month = getOrCreateMonth(event.block.timestamp); + + const dailyEarnFlow = getOrCreateDailyEarnFlow(day, token, protocol); + const weeklyEarnFlow = getOrCreateWeeklyEarnFlow(week, token, protocol); + const monthlyEarnFlow = getOrCreateMonthlyEarnFlow(month, token, protocol); + const earnPosition = getOrCreateEarnPosition( + account, + pool, + token, + protocol, + ); + + dailyEarnFlow.amountIn = dailyEarnFlow.amountIn.plus(amount); + weeklyEarnFlow.amountIn = weeklyEarnFlow.amountIn.plus(amount); + monthlyEarnFlow.amountIn = monthlyEarnFlow.amountIn.plus(amount); + earnPosition.invested = earnPosition.invested.plus(amount); + + dailyEarnFlow.save(); + weeklyEarnFlow.save(); + monthlyEarnFlow.save(); + earnPosition.save(); +} + +export function handleRedeem(event: RedeemEvent): void { + const account = ClaveAccount.load(event.params.redeemer); + if (!account) { + return; + } + + const pool = event.address; + const amount = event.params.redeemAmount; + + const day = getOrCreateDay(event.block.timestamp); + const week = getOrCreateWeek(event.block.timestamp); + const month = getOrCreateMonth(event.block.timestamp); + + const dailyEarnFlow = getOrCreateDailyEarnFlow(day, token, protocol); + const weeklyEarnFlow = getOrCreateWeeklyEarnFlow(week, token, protocol); + const monthlyEarnFlow = getOrCreateMonthlyEarnFlow(month, token, protocol); + const earnPosition = getOrCreateEarnPosition( + account, + pool, + token, + protocol, + ); + + const invested = earnPosition.invested; + + if (amount.gt(invested)) { + const compoundGain = amount.minus(invested); + dailyEarnFlow.amountOut = dailyEarnFlow.amountOut.plus(invested); + dailyEarnFlow.claimedGain = + dailyEarnFlow.claimedGain.plus(compoundGain); + weeklyEarnFlow.amountOut = weeklyEarnFlow.amountOut.plus(invested); + weeklyEarnFlow.claimedGain = + weeklyEarnFlow.claimedGain.plus(compoundGain); + monthlyEarnFlow.amountOut = monthlyEarnFlow.amountOut.plus(invested); + monthlyEarnFlow.claimedGain = + monthlyEarnFlow.claimedGain.plus(compoundGain); + earnPosition.invested = ZERO; + earnPosition.compoundGain = + earnPosition.compoundGain.plus(compoundGain); + } else { + dailyEarnFlow.amountOut = dailyEarnFlow.amountOut.plus(amount); + weeklyEarnFlow.amountOut = weeklyEarnFlow.amountOut.plus(amount); + monthlyEarnFlow.amountOut = monthlyEarnFlow.amountOut.plus(amount); + earnPosition.invested = invested.minus(amount); + } + + dailyEarnFlow.save(); + weeklyEarnFlow.save(); + monthlyEarnFlow.save(); + earnPosition.save(); +} diff --git a/subgraph/src/venus-pool-usdt.ts b/subgraph/src/venus-pool-usdt.ts new file mode 100644 index 0000000..c5c359e --- /dev/null +++ b/subgraph/src/venus-pool-usdt.ts @@ -0,0 +1,110 @@ +/* eslint-disable @typescript-eslint/consistent-type-imports */ +import { Address } from '@graphprotocol/graph-ts'; + +import { + Mint as MintEvent, + Redeem as RedeemEvent, +} from '../generated/VenusUsdt/VenusToken'; +import { ClaveAccount } from '../generated/schema'; +import { + ZERO, + getOrCreateDailyEarnFlow, + getOrCreateDay, + getOrCreateEarnPosition, + getOrCreateMonth, + getOrCreateMonthlyEarnFlow, + getOrCreateWeek, + getOrCreateWeeklyEarnFlow, +} from './helpers'; + +const protocol = 'Venus'; + +const token = Address.fromHexString( + '0x493257fD37EDB34451f62EDf8D2a0C418852bA4C', +); + +export function handleMint(event: MintEvent): void { + const account = ClaveAccount.load(event.params.minter); + if (!account) { + return; + } + + const pool = event.address; + const amount = event.params.mintAmount; + + const day = getOrCreateDay(event.block.timestamp); + const week = getOrCreateWeek(event.block.timestamp); + const month = getOrCreateMonth(event.block.timestamp); + + const dailyEarnFlow = getOrCreateDailyEarnFlow(day, token, protocol); + const weeklyEarnFlow = getOrCreateWeeklyEarnFlow(week, token, protocol); + const monthlyEarnFlow = getOrCreateMonthlyEarnFlow(month, token, protocol); + const earnPosition = getOrCreateEarnPosition( + account, + pool, + token, + protocol, + ); + + dailyEarnFlow.amountIn = dailyEarnFlow.amountIn.plus(amount); + weeklyEarnFlow.amountIn = weeklyEarnFlow.amountIn.plus(amount); + monthlyEarnFlow.amountIn = monthlyEarnFlow.amountIn.plus(amount); + earnPosition.invested = earnPosition.invested.plus(amount); + + dailyEarnFlow.save(); + weeklyEarnFlow.save(); + monthlyEarnFlow.save(); + earnPosition.save(); +} + +export function handleRedeem(event: RedeemEvent): void { + const account = ClaveAccount.load(event.params.redeemer); + if (!account) { + return; + } + + const pool = event.address; + const amount = event.params.redeemAmount; + + const day = getOrCreateDay(event.block.timestamp); + const week = getOrCreateWeek(event.block.timestamp); + const month = getOrCreateMonth(event.block.timestamp); + + const dailyEarnFlow = getOrCreateDailyEarnFlow(day, token, protocol); + const weeklyEarnFlow = getOrCreateWeeklyEarnFlow(week, token, protocol); + const monthlyEarnFlow = getOrCreateMonthlyEarnFlow(month, token, protocol); + const earnPosition = getOrCreateEarnPosition( + account, + pool, + token, + protocol, + ); + + const invested = earnPosition.invested; + + if (amount.gt(invested)) { + const compoundGain = amount.minus(invested); + dailyEarnFlow.amountOut = dailyEarnFlow.amountOut.plus(invested); + dailyEarnFlow.claimedGain = + dailyEarnFlow.claimedGain.plus(compoundGain); + weeklyEarnFlow.amountOut = weeklyEarnFlow.amountOut.plus(invested); + weeklyEarnFlow.claimedGain = + weeklyEarnFlow.claimedGain.plus(compoundGain); + monthlyEarnFlow.amountOut = monthlyEarnFlow.amountOut.plus(invested); + monthlyEarnFlow.claimedGain = + monthlyEarnFlow.claimedGain.plus(compoundGain); + earnPosition.invested = ZERO; + earnPosition.compoundGain = + earnPosition.compoundGain.plus(compoundGain); + } else { + dailyEarnFlow.amountOut = dailyEarnFlow.amountOut.plus(amount); + weeklyEarnFlow.amountOut = weeklyEarnFlow.amountOut.plus(amount); + monthlyEarnFlow.amountOut = monthlyEarnFlow.amountOut.plus(amount); + earnPosition.invested = invested.minus(amount); + } + + dailyEarnFlow.save(); + weeklyEarnFlow.save(); + monthlyEarnFlow.save(); + earnPosition.save(); +} diff --git a/subgraph/src/venus-reward.ts b/subgraph/src/venus-reward.ts new file mode 100644 index 0000000..f667fe3 --- /dev/null +++ b/subgraph/src/venus-reward.ts @@ -0,0 +1,55 @@ +/* eslint-disable @typescript-eslint/consistent-type-imports */ +import { Address } from '@graphprotocol/graph-ts'; + +import { DistributedSupplierRewardToken as DistributedSupplierRewardTokenEvent } from '../generated/VenusReward/VenusReward'; +import { ClaveAccount } from '../generated/schema'; +import { + getOrCreateDailyEarnFlow, + getOrCreateDay, + getOrCreateEarnPosition, + getOrCreateMonth, + getOrCreateMonthlyEarnFlow, + getOrCreateWeek, + getOrCreateWeeklyEarnFlow, +} from './helpers'; + +const protocol = 'Venus'; +const token = Address.fromHexString( + '0xD78ABD81a3D57712a3af080dc4185b698Fe9ac5A', +); + +export function handleDistributedSupplierRewardToken( + event: DistributedSupplierRewardTokenEvent, +): void { + const account = ClaveAccount.load(event.params.supplier); + if (!account) { + return; + } + + const pool = event.address; + const amount = event.params.rewardTokenTotal; + + const day = getOrCreateDay(event.block.timestamp); + const week = getOrCreateWeek(event.block.timestamp); + const month = getOrCreateMonth(event.block.timestamp); + + const dailyEarnFlow = getOrCreateDailyEarnFlow(day, token, protocol); + const weeklyEarnFlow = getOrCreateWeeklyEarnFlow(week, token, protocol); + const monthlyEarnFlow = getOrCreateMonthlyEarnFlow(month, token, protocol); + const earnPosition = getOrCreateEarnPosition( + account, + pool, + token, + protocol, + ); + + dailyEarnFlow.claimedGain = dailyEarnFlow.claimedGain.plus(amount); + weeklyEarnFlow.claimedGain = weeklyEarnFlow.claimedGain.plus(amount); + monthlyEarnFlow.claimedGain = monthlyEarnFlow.claimedGain.plus(amount); + earnPosition.normalGain = earnPosition.normalGain.plus(amount); + + dailyEarnFlow.save(); + weeklyEarnFlow.save(); + monthlyEarnFlow.save(); + earnPosition.save(); +} From 3e74c1dc502613762da459cca56040c18b7785b1 Mon Sep 17 00:00:00 2001 From: tahos81 Date: Wed, 2 Oct 2024 14:20:41 +0300 Subject: [PATCH 094/103] Update subgraph.yaml --- subgraph/subgraph.yaml | 73 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/subgraph/subgraph.yaml b/subgraph/subgraph.yaml index 753e355..8c902d5 100644 --- a/subgraph/subgraph.yaml +++ b/subgraph/subgraph.yaml @@ -453,6 +453,79 @@ dataSources: - event: RewardPaid(indexed address,uint256) handler: handleRewardPaid file: ./src/meow-staking.ts + - kind: ethereum + name: VenusUsdt + network: zksync-era + source: + address: '0x69cDA960E3b20DFD480866fFfd377Ebe40bd0A46' + abi: VenusToken + startBlock: 43552193 + mapping: + kind: ethereum/events + apiVersion: 0.0.7 + language: wasm/assemblyscript + entities: + - DailyEarnFlow + - WeeklyEarnFlow + - MonthlyEarnFlow + - EarnPosition + abis: + - name: VenusToken + file: ./abis/VenusToken.json + eventHandlers: + - event: Mint(indexed address,uint256,uint256,uint256) + handler: handleMint + - event: Redeem(indexed address,uint256,uint256,uint256) + handler: handleRedeem + file: ./src/venus-pool-usdt.ts + - kind: ethereum + name: VenusUsdce + network: zksync-era + source: + address: '0x1af23bd57c62a99c59ad48236553d0dd11e49d2d' + abi: VenusToken + startBlock: 43552199 + mapping: + kind: ethereum/events + apiVersion: 0.0.7 + language: wasm/assemblyscript + entities: + - DailyEarnFlow + - WeeklyEarnFlow + - MonthlyEarnFlow + - EarnPosition + abis: + - name: VenusToken + file: ./abis/VenusToken.json + eventHandlers: + - event: Mint(indexed address,uint256,uint256,uint256) + handler: handleMint + - event: Redeem(indexed address,uint256,uint256,uint256) + handler: handleRedeem + file: ./src/venus-pool-usdce.ts + - kind: ethereum + name: VenusReward + network: zksync-era + source: + address: '0x7C7846A74AB38A8d554Bc5f7652eCf8Efb58c894' + abi: VenusReward + startBlock: 44575350 + mapping: + kind: ethereum/events + apiVersion: 0.0.7 + language: wasm/assemblyscript + entities: + - DailyEarnFlow + - WeeklyEarnFlow + - MonthlyEarnFlow + - EarnPosition + abis: + - name: VenusReward + file: ./abis/VenusReward.json + eventHandlers: + - event: DistributedSupplierRewardToken(indexed address,indexed address,uint256,uint256,uint256) + handler: handleDistributedSupplierRewardToken + file: ./src/venus-reward.ts templates: - kind: ethereum name: Account From 753a79845ef1667b3549bf5ab297835b7ffd6e71 Mon Sep 17 00:00:00 2001 From: tahos81 Date: Wed, 2 Oct 2024 14:21:00 +0300 Subject: [PATCH 095/103] husky --- subgraph/src/venus-pool-usdce.ts | 5 +++++ subgraph/src/venus-pool-usdt.ts | 5 +++++ subgraph/src/venus-reward.ts | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/subgraph/src/venus-pool-usdce.ts b/subgraph/src/venus-pool-usdce.ts index da66087..768a4e5 100644 --- a/subgraph/src/venus-pool-usdce.ts +++ b/subgraph/src/venus-pool-usdce.ts @@ -1,3 +1,8 @@ +/** + * Copyright Clave - All Rights Reserved + * Unauthorized copying of this file, via any medium is strictly prohibited + * Proprietary and confidential + */ /* eslint-disable @typescript-eslint/consistent-type-imports */ import { Address } from '@graphprotocol/graph-ts'; diff --git a/subgraph/src/venus-pool-usdt.ts b/subgraph/src/venus-pool-usdt.ts index c5c359e..f9bcf20 100644 --- a/subgraph/src/venus-pool-usdt.ts +++ b/subgraph/src/venus-pool-usdt.ts @@ -1,3 +1,8 @@ +/** + * Copyright Clave - All Rights Reserved + * Unauthorized copying of this file, via any medium is strictly prohibited + * Proprietary and confidential + */ /* eslint-disable @typescript-eslint/consistent-type-imports */ import { Address } from '@graphprotocol/graph-ts'; diff --git a/subgraph/src/venus-reward.ts b/subgraph/src/venus-reward.ts index f667fe3..615ddb1 100644 --- a/subgraph/src/venus-reward.ts +++ b/subgraph/src/venus-reward.ts @@ -1,3 +1,8 @@ +/** + * Copyright Clave - All Rights Reserved + * Unauthorized copying of this file, via any medium is strictly prohibited + * Proprietary and confidential + */ /* eslint-disable @typescript-eslint/consistent-type-imports */ import { Address } from '@graphprotocol/graph-ts'; From 42e6ba4d8efd23d3cb1bce37e3a6fba7ed87aa56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Wed, 2 Oct 2024 17:51:16 +0300 Subject: [PATCH 096/103] [clave-contracts] feat: init social recovery tests, not working --- test/accounts/modules/socialrecovery.test.ts | 118 +++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 test/accounts/modules/socialrecovery.test.ts diff --git a/test/accounts/modules/socialrecovery.test.ts b/test/accounts/modules/socialrecovery.test.ts new file mode 100644 index 0000000..7d3cfbe --- /dev/null +++ b/test/accounts/modules/socialrecovery.test.ts @@ -0,0 +1,118 @@ +/** + * Copyright Clave - All Rights Reserved + * Unauthorized copying of this file, via any medium is strictly prohibited + * Proprietary and confidential + */ +import { assert, expect } from 'chai'; +import type { ec } from 'elliptic'; +import { AbiCoder } from 'ethers'; +import * as hre from 'hardhat'; +import type { Contract } from 'zksync-ethers'; +import { Provider, Wallet } from 'zksync-ethers'; + +import { LOCAL_RICH_WALLETS, getWallet } from '../../../deploy/utils'; +import { ClaveDeployer } from '../../utils/deployer'; +import { fixture } from '../../utils/fixture'; +import { addModule } from '../../utils/managers/modulemanager'; +import { VALIDATORS } from '../../utils/names'; +import { genKey } from '../../utils/p256'; + +describe('Clave Contracts - Manager tests', () => { + let deployer: ClaveDeployer; + let provider: Provider; + let richWallet: Wallet; + let teeValidator: Contract; + let account: Contract; + let keyPair: ec.KeyPair; + + let socialRecoveryModule: Contract; + + before(async () => { + richWallet = getWallet(hre, LOCAL_RICH_WALLETS[0].privateKey); + deployer = new ClaveDeployer(hre, richWallet); + provider = new Provider(hre.network.config.url, undefined, { + cacheTimeout: -1, + }); + + [, , , , teeValidator, account, keyPair] = await fixture( + deployer, + VALIDATORS.TEE, + ); + + const accountAddress = await account.getAddress(); + + await deployer.fund(10000, accountAddress); + + socialRecoveryModule = await deployer.deployCustomContract( + 'SocialRecoveryModule', + ['TEST', '0', 0, 1], + ); + }); + + describe('Module Tests - Social Recovery Module', () => { + let socialGuardian: Wallet; + let newKeyPair: ec.KeyPair; + + describe('Adding & Initializing module', () => { + before(async () => { + socialGuardian = new Wallet( + Wallet.createRandom().privateKey, + provider, + ); + + newKeyPair = genKey(); + }); + + it('should check existing modules', async () => { + expect(await account.listModules()).to.deep.eq([]); + }); + + it('should add a new module', async () => { + expect( + await account.isModule( + await socialRecoveryModule.getAddress(), + ), + ).to.be.false; + + const initData = AbiCoder.defaultAbiCoder().encode( + ['uint128', 'uin128', 'address[]'], + [0, 1, []], + ); + await addModule( + provider, + account, + teeValidator, + socialRecoveryModule, + initData, + keyPair, + ); + expect( + await account.isModule( + await socialRecoveryModule.getAddress(), + ), + ).to.be.true; + + const expectedModules = [ + await socialRecoveryModule.getAddress(), + ]; + expect(await account.listModules()).to.deep.eq(expectedModules); + }); + + it('should init the module successfully', async () => { + const status = await socialRecoveryModule.isInited( + await account.getAddress(), + ); + expect(status).to.eq(true); + }); + + it('should assign the guardian correctly', async () => { + const guardians = await socialRecoveryModule.getGuardians( + await account.getAddress(), + ); + expect(guardians).to.deep.eq([ + await socialGuardian.getAddress(), + ]); + }); + }); + }); +}); From c1e9f180f316a393454574b5da731681adacf73f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Fri, 4 Oct 2024 15:54:14 +0300 Subject: [PATCH 097/103] [clave-contracts] fix: struct encoding issue, not working --- test/accounts/modules/socialrecovery.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/accounts/modules/socialrecovery.test.ts b/test/accounts/modules/socialrecovery.test.ts index 7d3cfbe..834dce1 100644 --- a/test/accounts/modules/socialrecovery.test.ts +++ b/test/accounts/modules/socialrecovery.test.ts @@ -75,8 +75,8 @@ describe('Clave Contracts - Manager tests', () => { ).to.be.false; const initData = AbiCoder.defaultAbiCoder().encode( - ['uint128', 'uin128', 'address[]'], - [0, 1, []], + ['tuple(uint128, uint128, address[])'], + [[0, 1, [await socialGuardian.getAddress()]]], ); await addModule( provider, From 5845fc479d88e817ada850a75bc24c78eea47a57 Mon Sep 17 00:00:00 2001 From: tahos81 Date: Fri, 4 Oct 2024 17:01:50 +0300 Subject: [PATCH 098/103] fmt --- subgraph/src/venus-pool-usdce.ts | 1 + subgraph/src/venus-pool-usdt.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/subgraph/src/venus-pool-usdce.ts b/subgraph/src/venus-pool-usdce.ts index 768a4e5..034f5e8 100644 --- a/subgraph/src/venus-pool-usdce.ts +++ b/subgraph/src/venus-pool-usdce.ts @@ -3,6 +3,7 @@ * Unauthorized copying of this file, via any medium is strictly prohibited * Proprietary and confidential */ + /* eslint-disable @typescript-eslint/consistent-type-imports */ import { Address } from '@graphprotocol/graph-ts'; diff --git a/subgraph/src/venus-pool-usdt.ts b/subgraph/src/venus-pool-usdt.ts index f9bcf20..de242f9 100644 --- a/subgraph/src/venus-pool-usdt.ts +++ b/subgraph/src/venus-pool-usdt.ts @@ -3,6 +3,7 @@ * Unauthorized copying of this file, via any medium is strictly prohibited * Proprietary and confidential */ + /* eslint-disable @typescript-eslint/consistent-type-imports */ import { Address } from '@graphprotocol/graph-ts'; From 648746fa80ffe32a328852a276203626b47736a2 Mon Sep 17 00:00:00 2001 From: tahos81 Date: Fri, 4 Oct 2024 21:28:19 +0300 Subject: [PATCH 099/103] Update socialrecovery.test.ts --- test/accounts/modules/socialrecovery.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/accounts/modules/socialrecovery.test.ts b/test/accounts/modules/socialrecovery.test.ts index 834dce1..4ad52d2 100644 --- a/test/accounts/modules/socialrecovery.test.ts +++ b/test/accounts/modules/socialrecovery.test.ts @@ -45,7 +45,7 @@ describe('Clave Contracts - Manager tests', () => { socialRecoveryModule = await deployer.deployCustomContract( 'SocialRecoveryModule', - ['TEST', '0', 0, 1], + ['TEST', '0', 0, 0], ); }); @@ -76,7 +76,7 @@ describe('Clave Contracts - Manager tests', () => { const initData = AbiCoder.defaultAbiCoder().encode( ['tuple(uint128, uint128, address[])'], - [[0, 1, [await socialGuardian.getAddress()]]], + [[1, 1, [await socialGuardian.getAddress()]]], ); await addModule( provider, From ab1c70ef22ea4e14f989edb704ff31580237c350 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Fri, 4 Oct 2024 22:28:27 +0300 Subject: [PATCH 100/103] [clave-contracts] feat: update social recovery configs --- test/accounts/modules/socialrecovery.test.ts | 52 +++++++++++++++++++- test/utils/recovery/recovery.ts | 27 ++++++++++ 2 files changed, 78 insertions(+), 1 deletion(-) diff --git a/test/accounts/modules/socialrecovery.test.ts b/test/accounts/modules/socialrecovery.test.ts index 4ad52d2..7c54543 100644 --- a/test/accounts/modules/socialrecovery.test.ts +++ b/test/accounts/modules/socialrecovery.test.ts @@ -3,7 +3,7 @@ * Unauthorized copying of this file, via any medium is strictly prohibited * Proprietary and confidential */ -import { assert, expect } from 'chai'; +import { expect } from 'chai'; import type { ec } from 'elliptic'; import { AbiCoder } from 'ethers'; import * as hre from 'hardhat'; @@ -16,6 +16,7 @@ import { fixture } from '../../utils/fixture'; import { addModule } from '../../utils/managers/modulemanager'; import { VALIDATORS } from '../../utils/names'; import { genKey } from '../../utils/p256'; +import { updateSocialRecoveryConfig } from '../../utils/recovery/recovery'; describe('Clave Contracts - Manager tests', () => { let deployer: ClaveDeployer; @@ -51,6 +52,7 @@ describe('Clave Contracts - Manager tests', () => { describe('Module Tests - Social Recovery Module', () => { let socialGuardian: Wallet; + let secondGuardian: Wallet; let newKeyPair: ec.KeyPair; describe('Adding & Initializing module', () => { @@ -59,6 +61,10 @@ describe('Clave Contracts - Manager tests', () => { Wallet.createRandom().privateKey, provider, ); + secondGuardian = new Wallet( + Wallet.createRandom().privateKey, + provider, + ); newKeyPair = genKey(); }); @@ -113,6 +119,50 @@ describe('Clave Contracts - Manager tests', () => { await socialGuardian.getAddress(), ]); }); + + it('should change the guardian correctly', async () => { + await updateSocialRecoveryConfig( + provider, + account, + socialRecoveryModule, + teeValidator, + [1, 1, [await secondGuardian.getAddress()]], + keyPair, + ); + + const guardians = await socialRecoveryModule.getGuardians( + await account.getAddress(), + ); + expect(guardians).to.deep.eq([ + await secondGuardian.getAddress(), + ]); + }); + + it('should add multiple guardians correctly', async () => { + await updateSocialRecoveryConfig( + provider, + account, + socialRecoveryModule, + teeValidator, + [ + 1, + 1, + [ + await socialGuardian.getAddress(), + await secondGuardian.getAddress(), + ], + ], + keyPair, + ); + + const guardians = await socialRecoveryModule.getGuardians( + await account.getAddress(), + ); + expect(guardians).to.deep.eq([ + await socialGuardian.getAddress(), + await secondGuardian.getAddress(), + ]); + }); }); }); }); diff --git a/test/utils/recovery/recovery.ts b/test/utils/recovery/recovery.ts index ef7fa12..63f4fa5 100644 --- a/test/utils/recovery/recovery.ts +++ b/test/utils/recovery/recovery.ts @@ -99,6 +99,33 @@ export async function updateCloudGuardian( await txReceipt.wait(); } +export async function updateSocialRecoveryConfig( + provider: Provider, + account: Contract, + module: Contract, + validator: Contract, + config: [number, number, Array], + keyPair: ec.KeyPair, +): Promise { + const updateConfigTx = await module.updateConfig.populateTransaction({ + threshold: config[0], + timelock: config[1], + guardians: config[2], + }); + + const tx = await prepareTeeTx( + provider, + account, + updateConfigTx, + await validator.getAddress(), + keyPair, + ); + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + await txReceipt.wait(); +} + export async function executeRecovery( account: Contract, module: Contract, From 12adfe770966268ea2a6d7b2b171d6670abce994 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Sat, 5 Oct 2024 23:04:12 +0300 Subject: [PATCH 101/103] [clave-contracts] feat: start-stop social recovery --- test/accounts/modules/cloudrecovery.test.ts | 8 ++-- test/accounts/modules/socialrecovery.test.ts | 50 +++++++++++++++++++- test/utils/recovery/recovery.ts | 44 +++++++++++++++-- 3 files changed, 92 insertions(+), 10 deletions(-) diff --git a/test/accounts/modules/cloudrecovery.test.ts b/test/accounts/modules/cloudrecovery.test.ts index 382ec31..d29a071 100644 --- a/test/accounts/modules/cloudrecovery.test.ts +++ b/test/accounts/modules/cloudrecovery.test.ts @@ -18,7 +18,7 @@ import { VALIDATORS } from '../../utils/names'; import { encodePublicKey, genKey } from '../../utils/p256'; import { executeRecovery, - startRecovery, + startCloudRecovery, stopRecovery, updateCloudGuardian, } from '../../utils/recovery/recovery'; @@ -127,11 +127,10 @@ describe('Clave Contracts - Manager tests', () => { ), ).to.be.false; - await startRecovery( + await startCloudRecovery( cloudGuardian, account, cloudRecoveryModule, - teeValidator, encodePublicKey(newKeyPair), ); @@ -173,11 +172,10 @@ describe('Clave Contracts - Manager tests', () => { ), ).to.be.false; - await startRecovery( + await startCloudRecovery( cloudGuardian, account, cloudRecoveryModule, - teeValidator, encodePublicKey(newNewKeyPair), ); diff --git a/test/accounts/modules/socialrecovery.test.ts b/test/accounts/modules/socialrecovery.test.ts index 7c54543..a7f5450 100644 --- a/test/accounts/modules/socialrecovery.test.ts +++ b/test/accounts/modules/socialrecovery.test.ts @@ -15,8 +15,12 @@ import { ClaveDeployer } from '../../utils/deployer'; import { fixture } from '../../utils/fixture'; import { addModule } from '../../utils/managers/modulemanager'; import { VALIDATORS } from '../../utils/names'; -import { genKey } from '../../utils/p256'; -import { updateSocialRecoveryConfig } from '../../utils/recovery/recovery'; +import { encodePublicKey, genKey } from '../../utils/p256'; +import { + startSocialRecovery, + stopRecovery, + updateSocialRecoveryConfig, +} from '../../utils/recovery/recovery'; describe('Clave Contracts - Manager tests', () => { let deployer: ClaveDeployer; @@ -164,5 +168,47 @@ describe('Clave Contracts - Manager tests', () => { ]); }); }); + + describe('Recovering account', () => { + it('should start the recovery process by guardian', async () => { + const accountAddress = await account.getAddress(); + + const isRecoveringBefore = + await socialRecoveryModule.isRecovering(accountAddress); + expect(isRecoveringBefore).to.be.false; + + await startSocialRecovery( + socialGuardian, + account, + socialRecoveryModule, + newKeyPair, + ); + + const isRecoveringAfter = + await socialRecoveryModule.isRecovering(accountAddress); + expect(isRecoveringAfter).to.be.true; + }); + + it('should decline the social recovery', async () => { + const accountAddress = await account.getAddress(); + const isRecoveringBefore = + await socialRecoveryModule.isRecovering(accountAddress); + expect(isRecoveringBefore).to.be.true; + + await stopRecovery( + provider, + account, + socialRecoveryModule, + teeValidator, + keyPair, + ); + + const isRecoveringAfter = + await socialRecoveryModule.isRecovering(accountAddress); + expect(isRecoveringAfter).to.be.false; + }); + + it('should execute the social recovery', async () => {}); + }); }); }); diff --git a/test/utils/recovery/recovery.ts b/test/utils/recovery/recovery.ts index 63f4fa5..92eb42d 100644 --- a/test/utils/recovery/recovery.ts +++ b/test/utils/recovery/recovery.ts @@ -7,6 +7,7 @@ import type { ec } from 'elliptic'; import type { Contract, Provider, Wallet } from 'zksync-ethers'; import { utils } from 'zksync-ethers'; +import { encodePublicKey } from '../p256'; import { prepareTeeTx } from '../transactions'; type StartRecoveryParams = { @@ -15,8 +16,14 @@ type StartRecoveryParams = { nonce: number; }; +type RecoveryData = { + recoveringAddress: string; + newOwner: string; + nonce: number; +}; + async function signRecoveryEIP712Hash( - params: StartRecoveryParams, + params: StartRecoveryParams | RecoveryData, recoveryContract: Contract, wallet: Wallet, ): Promise { @@ -25,11 +32,10 @@ async function signRecoveryEIP712Hash( return signature; } -export async function startRecovery( +export async function startCloudRecovery( cloudGuardian: Wallet, account: Contract, module: Contract, - validator: Contract, newOwner: string, ): Promise { const recoveringAddress = await account.getAddress(); @@ -51,6 +57,38 @@ export async function startRecovery( await startRecoveryTx.wait(); } +export async function startSocialRecovery( + socialGuardian: Wallet, + account: Contract, + module: Contract, + newKeyPair: ec.KeyPair, +): Promise { + const recoveringAddress = await account.getAddress(); + const recoveryNonce = await module.recoveryNonces(recoveringAddress); + + const recoveryData = { + recoveringAddress: await account.getAddress(), + newOwner: encodePublicKey(newKeyPair), + nonce: recoveryNonce, + }; + + const signature = await signRecoveryEIP712Hash( + recoveryData, + module, + socialGuardian, + ); + + const guardianData = { + guardian: await socialGuardian.getAddress(), + signature: signature, + }; + + const startRecoveryTx = await module.startRecovery(recoveryData, [ + guardianData, + ]); + await startRecoveryTx.wait(); +} + export async function stopRecovery( provider: Provider, account: Contract, From b0c7ffeb70be883311b328727a364bc27e6a154a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Sat, 5 Oct 2024 23:41:53 +0300 Subject: [PATCH 102/103] [clave-contracts] feat: execute social recovery --- test/accounts/modules/socialrecovery.test.ts | 62 ++++++++++++++++++-- 1 file changed, 58 insertions(+), 4 deletions(-) diff --git a/test/accounts/modules/socialrecovery.test.ts b/test/accounts/modules/socialrecovery.test.ts index a7f5450..2b947c7 100644 --- a/test/accounts/modules/socialrecovery.test.ts +++ b/test/accounts/modules/socialrecovery.test.ts @@ -5,22 +5,24 @@ */ import { expect } from 'chai'; import type { ec } from 'elliptic'; -import { AbiCoder } from 'ethers'; +import { AbiCoder, parseEther } from 'ethers'; import * as hre from 'hardhat'; import type { Contract } from 'zksync-ethers'; -import { Provider, Wallet } from 'zksync-ethers'; +import { Provider, Wallet, utils } from 'zksync-ethers'; import { LOCAL_RICH_WALLETS, getWallet } from '../../../deploy/utils'; import { ClaveDeployer } from '../../utils/deployer'; import { fixture } from '../../utils/fixture'; import { addModule } from '../../utils/managers/modulemanager'; import { VALIDATORS } from '../../utils/names'; -import { encodePublicKey, genKey } from '../../utils/p256'; +import { genKey } from '../../utils/p256'; import { + executeRecovery, startSocialRecovery, stopRecovery, updateSocialRecoveryConfig, } from '../../utils/recovery/recovery'; +import { ethTransfer, prepareTeeTx } from '../../utils/transactions'; describe('Clave Contracts - Manager tests', () => { let deployer: ClaveDeployer; @@ -191,6 +193,7 @@ describe('Clave Contracts - Manager tests', () => { it('should decline the social recovery', async () => { const accountAddress = await account.getAddress(); + const isRecoveringBefore = await socialRecoveryModule.isRecovering(accountAddress); expect(isRecoveringBefore).to.be.true; @@ -208,7 +211,58 @@ describe('Clave Contracts - Manager tests', () => { expect(isRecoveringAfter).to.be.false; }); - it('should execute the social recovery', async () => {}); + it('should execute the social recovery', async () => { + const accountAddress = await account.getAddress(); + + const isRecoveringBefore = + await socialRecoveryModule.isRecovering(accountAddress); + expect(isRecoveringBefore).to.be.false; + + await startSocialRecovery( + socialGuardian, + account, + socialRecoveryModule, + newKeyPair, + ); + + const isRecoveringAfter = + await socialRecoveryModule.isRecovering(accountAddress); + expect(isRecoveringAfter).to.be.true; + + await executeRecovery(account, socialRecoveryModule); + + const isRecoveringAfterExecution = + await socialRecoveryModule.isRecovering(accountAddress); + expect(isRecoveringAfterExecution).to.be.false; + }); + + it('should send tx with new keys after recovery', async () => { + const amount = parseEther('1'); + const richAddress = await richWallet.getAddress(); + + const richBalanceBefore = await provider.getBalance( + richAddress, + ); + + const txData = ethTransfer(richAddress, amount); + const tx = await prepareTeeTx( + provider, + account, + txData, + await teeValidator.getAddress(), + newKeyPair, + ); + const txReceipt = await provider.broadcastTransaction( + utils.serializeEip712(tx), + ); + await txReceipt.wait(); + + const richBalanceAfter = await provider.getBalance(richAddress); + + expect(richBalanceAfter).to.be.equal( + richBalanceBefore + amount, + ); + }); }); }); }); From 8230af1696223e240e3c9ccb74bac12cfccf4d40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= Date: Mon, 7 Oct 2024 02:09:35 +0300 Subject: [PATCH 103/103] [clave-contracts] refactor: check keys before and after recovery in module tests --- test/accounts/modules/cloudrecovery.test.ts | 12 ++++++++++++ test/accounts/modules/socialrecovery.test.ts | 14 +++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/test/accounts/modules/cloudrecovery.test.ts b/test/accounts/modules/cloudrecovery.test.ts index d29a071..f883711 100644 --- a/test/accounts/modules/cloudrecovery.test.ts +++ b/test/accounts/modules/cloudrecovery.test.ts @@ -127,6 +127,10 @@ describe('Clave Contracts - Manager tests', () => { ), ).to.be.false; + expect(await account.r1ListOwners()).to.deep.eq([ + encodePublicKey(keyPair), + ]); + await startCloudRecovery( cloudGuardian, account, @@ -148,6 +152,10 @@ describe('Clave Contracts - Manager tests', () => { ), ).to.be.true; + expect(await account.r1ListOwners()).to.deep.eq([ + encodePublicKey(keyPair), + ]); + await executeRecovery(account, cloudRecoveryModule); expect( @@ -155,6 +163,10 @@ describe('Clave Contracts - Manager tests', () => { await account.getAddress(), ), ).to.be.false; + + expect(await account.r1ListOwners()).to.deep.eq([ + encodePublicKey(newKeyPair), + ]); }); }); diff --git a/test/accounts/modules/socialrecovery.test.ts b/test/accounts/modules/socialrecovery.test.ts index 2b947c7..994768a 100644 --- a/test/accounts/modules/socialrecovery.test.ts +++ b/test/accounts/modules/socialrecovery.test.ts @@ -15,7 +15,7 @@ import { ClaveDeployer } from '../../utils/deployer'; import { fixture } from '../../utils/fixture'; import { addModule } from '../../utils/managers/modulemanager'; import { VALIDATORS } from '../../utils/names'; -import { genKey } from '../../utils/p256'; +import { encodePublicKey, genKey } from '../../utils/p256'; import { executeRecovery, startSocialRecovery, @@ -179,6 +179,10 @@ describe('Clave Contracts - Manager tests', () => { await socialRecoveryModule.isRecovering(accountAddress); expect(isRecoveringBefore).to.be.false; + expect(await account.r1ListOwners()).to.deep.eq([ + encodePublicKey(keyPair), + ]); + await startSocialRecovery( socialGuardian, account, @@ -229,11 +233,19 @@ describe('Clave Contracts - Manager tests', () => { await socialRecoveryModule.isRecovering(accountAddress); expect(isRecoveringAfter).to.be.true; + expect(await account.r1ListOwners()).to.deep.eq([ + encodePublicKey(keyPair), + ]); + await executeRecovery(account, socialRecoveryModule); const isRecoveringAfterExecution = await socialRecoveryModule.isRecovering(accountAddress); expect(isRecoveringAfterExecution).to.be.false; + + expect(await account.r1ListOwners()).to.deep.eq([ + encodePublicKey(newKeyPair), + ]); }); it('should send tx with new keys after recovery', async () => {