From ab2e8a3d381c3c636bc3d4d4cd28aaf69bfa81be Mon Sep 17 00:00:00 2001 From: Nazarii Kyselevych Date: Thu, 16 May 2024 14:40:06 +0300 Subject: [PATCH 1/4] feat: diva abi --- src/abi/divETH.json | 852 +++++++++++++++++++++++++++++++++++++++++++ src/abi/wdivETH.json | 406 +++++++++++++++++++++ 2 files changed, 1258 insertions(+) create mode 100644 src/abi/divETH.json create mode 100644 src/abi/wdivETH.json diff --git a/src/abi/divETH.json b/src/abi/divETH.json new file mode 100644 index 000000000..03ae980a4 --- /dev/null +++ b/src/abi/divETH.json @@ -0,0 +1,852 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_accountingManager", + "type": "address" + } + ], + "stateMutability": "payable", + "type": "constructor" + }, + { + "inputs": [], + "name": "AccessDenied", + "type": "error" + }, + { + "inputs": [], + "name": "AllowanceOverflow", + "type": "error" + }, + { + "inputs": [], + "name": "AllowanceUnderflow", + "type": "error" + }, + { + "inputs": [], + "name": "AlreadyInitialized", + "type": "error" + }, + { + "inputs": [], + "name": "ErrMinDepositAmount", + "type": "error" + }, + { + "inputs": [], + "name": "ErrZeroAmount", + "type": "error" + }, + { + "inputs": [], + "name": "ErrZeroDeposit", + "type": "error" + }, + { + "inputs": [], + "name": "ErrorHighOperatorFee", + "type": "error" + }, + { + "inputs": [], + "name": "InsufficientAllowance", + "type": "error" + }, + { + "inputs": [], + "name": "InsufficientBalance", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidPermit", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidSignature", + "type": "error" + }, + { + "inputs": [], + "name": "NewOwnerIsZeroAddress", + "type": "error" + }, + { + "inputs": [], + "name": "NoHandoverRequest", + "type": "error" + }, + { + "inputs": [], + "name": "OnlyValidatorManager", + "type": "error" + }, + { + "inputs": [], + "name": "PermitExpired", + "type": "error" + }, + { + "inputs": [], + "name": "TotalSupplyOverflow", + "type": "error" + }, + { + "inputs": [], + "name": "Unauthorized", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddress", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAmount", + "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": "pendingOwner", + "type": "address" + } + ], + "name": "OwnershipHandoverCanceled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "pendingOwner", + "type": "address" + } + ], + "name": "OwnershipHandoverRequested", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "oldOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "reportTimestamp", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "timeElapsed", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "preTotalShares", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "preTotalEther", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "postTotalShares", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "postTotalEther", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "sharesMintedAsFees", + "type": "uint256" + } + ], + "name": "TokenRebased", + "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" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "sharesValue", + "type": "uint256" + } + ], + "name": "TransferShares", + "type": "event" + }, + { + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [ + { + "internalType": "bytes32", + "name": "result", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "result", + "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": "user", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "name": "burnShares", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "cancelOwnershipHandover", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pendingOwner", + "type": "address" + } + ], + "name": "completeOwnershipHandover", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "name": "convertToAssets", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "assets", + "type": "uint256" + } + ], + "name": "convertToShares", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "deposit", + "outputs": [ + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + } + ], + "name": "depositFor", + "outputs": [ + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "name": "mintShares", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "nonces", + "outputs": [ + { + "internalType": "uint256", + "name": "result", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "result", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pendingOwner", + "type": "address" + } + ], + "name": "ownershipHandoverExpiresAt", + "outputs": [ + { + "internalType": "uint256", + "name": "result", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "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": "uint256", + "name": "reportTimestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "timeElapsed", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "postTotalEther", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sharesMintedAsFees", + "type": "uint256" + } + ], + "name": "rebase", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "requestOwnershipHandover", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + } + ], + "name": "sharesOf", + "outputs": [ + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "totalEther", + "outputs": [ + { + "internalType": "uint128", + "name": "", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalShares", + "outputs": [ + { + "internalType": "uint128", + "name": "", + "type": "uint128" + } + ], + "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": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "name": "transferShares", + "outputs": [ + { + "internalType": "uint256", + "name": "assets", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "name": "transferSharesFrom", + "outputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "withdrawEther", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } +] diff --git a/src/abi/wdivETH.json b/src/abi/wdivETH.json new file mode 100644 index 000000000..a43aaee73 --- /dev/null +++ b/src/abi/wdivETH.json @@ -0,0 +1,406 @@ +[ + { + "inputs": [ + { + "internalType": "contract IConfigurationContract", + "name": "_configurationContract", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "AllowanceOverflow", + "type": "error" + }, + { + "inputs": [], + "name": "AllowanceUnderflow", + "type": "error" + }, + { + "inputs": [], + "name": "ErrZeroAmount", + "type": "error" + }, + { + "inputs": [], + "name": "InsufficientAllowance", + "type": "error" + }, + { + "inputs": [], + "name": "InsufficientBalance", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidPermit", + "type": "error" + }, + { + "inputs": [], + "name": "PermitExpired", + "type": "error" + }, + { + "inputs": [], + "name": "TotalSupplyOverflow", + "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": "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": "DIVAETHER", + "outputs": [ + { + "internalType": "contract IDivaEtherToken", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [ + { + "internalType": "bytes32", + "name": "result", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "result", + "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": "owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "result", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "depositETH", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "nonces", + "outputs": [ + { + "internalType": "uint256", + "name": "result", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "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": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "result", + "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": [ + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "name": "unwrap", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "wrap", + "outputs": [ + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } +] From b69d3c3fdacf7ebca6f9dd3df57918825b5fea10 Mon Sep 17 00:00:00 2001 From: Nazarii Kyselevych Date: Thu, 16 May 2024 14:40:29 +0300 Subject: [PATCH 2/4] feat: diva diveth pool --- src/dex/diva/diveth-pool.ts | 78 +++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 src/dex/diva/diveth-pool.ts diff --git a/src/dex/diva/diveth-pool.ts b/src/dex/diva/diveth-pool.ts new file mode 100644 index 000000000..d7c4a77ee --- /dev/null +++ b/src/dex/diva/diveth-pool.ts @@ -0,0 +1,78 @@ +import { StatefulEventSubscriber } from '../../stateful-event-subscriber'; +import { Logger } from 'log4js'; +import { IDexHelper } from '../../dex-helper'; +import { Address } from '@paraswap/core'; +import { Interface } from '@ethersproject/abi'; +import { AsyncOrSync, DeepReadonly } from 'ts-essentials'; +import { Log } from '../../types'; +import { DivETHPoolState } from './types'; +import { ethers } from 'ethers'; + +export class DivETHEventPool extends StatefulEventSubscriber { + decoder = (log: Log) => this.divaETHInterface.parseLog(log); + + constructor( + parentName: string, + protected dexHelper: IDexHelper, + private divaETHAddress: Address, + private divaETHInterface: Interface, + logger: Logger, + ) { + super(parentName, 'diveth', dexHelper, logger); + this.addressesSubscribed = [divaETHAddress]; + } + + protected processLog( + state: DeepReadonly, + log: Readonly, + ): AsyncOrSync | null> { + const event = this.decoder(log); + if (event.name === 'TokenRebased') + return { + totalShares: BigInt(event.args.postTotalShares), + totalEther: BigInt(event.args.postTotalEther), + }; + + return null; + } + + + async generateState(blockNumber: number | 'latest' = 'latest'): Promise> { + const data: { returnData: any[] } = await this.dexHelper.multiContract.methods + .aggregate([ + { + target: this.divaETHAddress, + callData: this.divaETHInterface.encodeFunctionData('totalEther', []), + }, + { + target: this.divaETHAddress, + callData: this.divaETHInterface.encodeFunctionData('totalShares', []), + }, + ]) + .call({}, blockNumber); + + const decodedData = data.returnData.map(d => ethers.utils.defaultAbiCoder.decode(['uint256'], d)); + const [totalEther, totalShares] = decodedData.map((d) => BigInt(d[0].toString())); + + return { + totalEther, + totalShares, + }; + } + + convertToShares(blockNumber: number, assets: bigint): bigint { + const state = this.getState(blockNumber); + if (!state) throw new Error('Cannot compute price'); + const { totalEther, totalShares } = state; + + return (assets * totalShares) / totalEther; + } + + convertToAssets(blockNumber: number, shares: bigint): bigint { + const state = this.getState(blockNumber); + if (!state) throw new Error('Cannot compute price'); + const { totalEther, totalShares } = state; + + return (shares * totalEther) / totalShares; + } +} From c660be2aaf0d7e82b6befd2219cf6968825d38b8 Mon Sep 17 00:00:00 2001 From: Nazarii Kyselevych Date: Thu, 16 May 2024 14:42:54 +0300 Subject: [PATCH 3/4] feat: diva dex template --- src/dex/diva/config.ts | 19 ++++ src/dex/diva/diva.ts | 231 +++++++++++++++++++++++++++++++++++++++++ src/dex/diva/types.ts | 10 ++ 3 files changed, 260 insertions(+) create mode 100644 src/dex/diva/config.ts create mode 100644 src/dex/diva/diva.ts create mode 100644 src/dex/diva/types.ts diff --git a/src/dex/diva/config.ts b/src/dex/diva/config.ts new file mode 100644 index 000000000..ba2b3ceea --- /dev/null +++ b/src/dex/diva/config.ts @@ -0,0 +1,19 @@ +import { AdapterMappings, DexConfigMap } from '../../types'; +import { DivaParams } from './types'; +import { Network, SwapSide } from '../../constants'; + +export const DivaConfig: DexConfigMap = { + Diva: { + [Network.MAINNET]: { // currently only on sepolia + divETH: '0x78Cb3BE3ee9C7aD14967aD10F9D2baFA79F2DC94', + wdivETH: '0x3F806A22dc942934c66DeF5FF8eC45a89A5aF454', + }, + }, +}; + +export const Adapters: Record = { + [Network.MAINNET]: { + [SwapSide.SELL]: [{ name: 'AvalancheAdapter02', index: 6 }], + [SwapSide.BUY]: [{ name: 'AvalancheBuyAdapter', index: 8 }], + }, +}; diff --git a/src/dex/diva/diva.ts b/src/dex/diva/diva.ts new file mode 100644 index 000000000..c119f1876 --- /dev/null +++ b/src/dex/diva/diva.ts @@ -0,0 +1,231 @@ +import { Interface, JsonFragment } from '@ethersproject/abi'; +import { NumberAsString, SwapSide } from '@paraswap/core'; +import { + AdapterExchangeParam, + Address, + ExchangePrices, + Logger, + PoolLiquidity, + PoolPrices, + SimpleExchangeParam, + Token, + TransferFeeParams, +} from '../../types'; +import { IDex } from '../idex'; +import DIVETH_ABI from '../../abi/divETH.json'; +import WDIVETH_ABI from '../../abi/wdivETH.json'; +import { ETHER_ADDRESS, Network } from '../../constants'; +import { IDexHelper } from '../../dex-helper'; +import { SimpleExchange } from '../simple-exchange'; +import { BI_POWS } from '../../bigint-constants'; +import { AsyncOrSync } from 'ts-essentials'; +import { getDexKeysWithNetwork, isETHAddress } from '../../utils'; +import { WethFunctions } from '../weth/types'; +import * as CALLDATA_GAS_COST from '../../calldata-gas-cost'; +import _ from 'lodash'; +import { DivaConfig, Adapters } from './config'; +import { DivETHEventPool } from './diveth-pool'; + +export enum swETHFunctions { + deposit = 'deposit', +} + +export type SwellData = {}; +export type SwellParams = {}; + +export class Diva + extends SimpleExchange + implements IDex +{ + static dexKeys = ['Diva']; + divETHAddress: string; + wdivETHAddress: string; + divETHInterface: Interface; + wdivETHInterface: Interface; + needWrapNative = false; + hasConstantPriceLargeAmounts: boolean = false; + divETHEventPool: DivETHEventPool; + logger: Logger; + + public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = + getDexKeysWithNetwork(_.pick(DivaConfig, ['Swell'])); + + constructor( + protected network: Network, + dexKey: string, + protected dexHelper: IDexHelper, + protected config = DivaConfig[dexKey][network], + protected adapters = Adapters[network], + ) { + super(dexHelper, 'Diva'); + + this.network = dexHelper.config.data.network; + this.divETHInterface = new Interface(DIVETH_ABI as JsonFragment[]); + this.wdivETHInterface = new Interface(WDIVETH_ABI as JsonFragment[]); + this.divETHAddress = this.config.divETH.toLowerCase(); + this.wdivETHAddress = this.config.wdivETH.toLowerCase(); + this.logger = dexHelper.getLogger(this.dexKey); + this.divETHEventPool = new DivETHEventPool( + this.dexKey, + dexHelper, + this.divETHAddress, + this.divETHInterface, + this.logger, + ); + } + + async initializePricing(blockNumber: number) { + await this.divETHEventPool.initialize(blockNumber); + } + + getPoolIdentifierKey(): string { + return `${ETHER_ADDRESS}_${this.divETHEventPool}`.toLowerCase(); + } + + isEligibleSwap( + srcToken: Token | string, + destToken: Token | string, + side: SwapSide, + ): boolean { + if (side === SwapSide.BUY) return false; + + const srcTokenAddress = ( + typeof srcToken === 'string' ? srcToken : srcToken.address + ).toLowerCase(); + const destTokenAddress = ( + typeof destToken === 'string' ? destToken : destToken.address + ).toLowerCase(); + + return ( + (isETHAddress(srcTokenAddress) || this.isWETH(srcTokenAddress)) && + destTokenAddress === this.divETHAddress + ); + } + + assertEligibility( + srcToken: Token | string, + destToken: Token | string, + side: SwapSide, + ) { + if (!this.isEligibleSwap(srcToken, destToken, side)) { + throw new Error('Only eth -> divETH/wdivETH and divETH <-> wdivETH swaps are supported'); + } + } + + async getPoolIdentifiers( + srcToken: Token, + destToken: Token, + side: SwapSide, + blockNumber: number, + ): Promise { + return this.isEligibleSwap(srcToken, destToken, side) + ? [this.getPoolIdentifierKey()] + : []; + } + + async getPricesVolume( + srcToken: Token, + destToken: Token, + amountsIn: bigint[], + side: SwapSide, + blockNumber: number, + limitPools?: string[] | undefined, + transferFees?: TransferFeeParams | undefined, + isFirstSwap?: boolean | undefined, + ): Promise | null> { + if (!this.isEligibleSwap(srcToken, destToken, side)) return null; + if (this.divETHEventPool.getState(blockNumber) === null) return null; + + return null; + + // const unitIn = BI_POWS[18]; + // const unitOut = this.divETHEventPool.getPrice(blockNumber, unitIn); + // const amountsOut = amountsIn.map(amountIn => + // this.divETHEventPool.getPrice(blockNumber, amountIn), + // ); + // + // return [ + // { + // prices: amountsOut, + // unit: unitOut, + // data: {}, + // exchange: this.dexKey, + // poolIdentifier: this.getPoolIdentifierKey(), + // gasCost: 120_000, + // poolAddresses: [this.swETHAddress], + // }, + // ]; + } + + getAdapterParam( + srcToken: Address, + destToken: Address, + srcAmount: NumberAsString, + destAmount: NumberAsString, + data: SwellData, + side: SwapSide, + ): AdapterExchangeParam { + this.assertEligibility(srcToken, destToken, side); + + return { + targetExchange: this.divETHAddress, // not used contract side + payload: '0x', + networkFee: '0', + }; + } + + async getSimpleParam( + srcToken: Address, + destToken: Address, + srcAmount: NumberAsString, + destAmount: NumberAsString, + data: SwellData, + side: SwapSide, + ): Promise { + this.assertEligibility(srcToken, destToken, side); + + const callees = []; + const calldata = []; + const values = []; + + if (this.isWETH(srcToken)) { + // note: apparently ERC20 ABI contains wETH fns (deposit() and withdraw()) + const wethUnwrapData = this.erc20Interface.encodeFunctionData( + WethFunctions.withdraw, + [srcAmount], + ); + callees.push(this.dexHelper.config.data.wrappedNativeTokenAddress); + calldata.push(wethUnwrapData); + values.push('0'); + } + + const swapData = this.divETHInterface.encodeFunctionData( + swETHFunctions.deposit, + [], + ); + + callees.push(this.divETHAddress); + calldata.push(swapData); + values.push(srcAmount); + + return { + callees, + calldata, + values, + networkFee: '0', + }; + } + + getCalldataGasCost(poolPrices: PoolPrices): number | number[] { + return CALLDATA_GAS_COST.DEX_OVERHEAD + CALLDATA_GAS_COST.LENGTH_SMALL; + } + getAdapters(side: SwapSide): { name: string; index: number }[] | null { + return this.adapters?.[side] || null; + } + getTopPoolsForToken( + tokenAddress: string, + limit: number, + ): AsyncOrSync { + return []; + } +} diff --git a/src/dex/diva/types.ts b/src/dex/diva/types.ts new file mode 100644 index 000000000..e02485339 --- /dev/null +++ b/src/dex/diva/types.ts @@ -0,0 +1,10 @@ +export type DivaParams = { + divETH: string; + wdivETH: string; +}; + +export type DivETHPoolState = { + totalShares: bigint; + totalEther: bigint; +} + From 7ee898a6a0abab10a9ecb97408df74810837ea4b Mon Sep 17 00:00:00 2001 From: Nazarii Kyselevych Date: Fri, 17 May 2024 11:50:19 +0300 Subject: [PATCH 4/4] feat: diva dex --- src/dex/diva/diva-e2e.test.ts | 81 +++++++++++++++++++++++++++++++++++ src/dex/diva/diva.ts | 66 ++++++++++++---------------- src/dex/index.ts | 2 + tests/constants-e2e.ts | 12 ++++++ 4 files changed, 123 insertions(+), 38 deletions(-) create mode 100644 src/dex/diva/diva-e2e.test.ts diff --git a/src/dex/diva/diva-e2e.test.ts b/src/dex/diva/diva-e2e.test.ts new file mode 100644 index 000000000..eec3f21f8 --- /dev/null +++ b/src/dex/diva/diva-e2e.test.ts @@ -0,0 +1,81 @@ +import dotenv from 'dotenv'; +dotenv.config(); + +import { testE2E } from '../../../tests/utils-e2e'; +import { Tokens, Holders } from '../../../tests/constants-e2e'; +import { Network, ContractMethod, SwapSide } from '../../constants'; +import { StaticJsonRpcProvider } from '@ethersproject/providers'; +import { generateConfig } from '../../config'; + +describe('Diva', () => { + const network = Network.MAINNET; + const tokens = Tokens[network]; + const holders = Holders[network]; + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + const dexKey = 'Diva'; + + [ + ContractMethod.simpleSwap, + ContractMethod.multiSwap, + ContractMethod.megaSwap, + ].forEach(contractMethod => { + it(`${contractMethod} - ETH -> divWETH`, async () => { + await testE2E( + tokens.ETH, + tokens.SWETH, + holders.ETH, + '1000000000000000000', + SwapSide.SELL, + dexKey, + contractMethod, + network, + provider, + ); + }); + + it(`${contractMethod} - WETH -> divWETH`, async () => { + await testE2E( + tokens.WETH, + tokens.SWETH, + holders.WETH, + '1000000000000000000', + SwapSide.SELL, + dexKey, + contractMethod, + network, + provider, + ); + }); + + it(`${contractMethod} - ETH -> wdivWETH`, async () => { + await testE2E( + tokens.ETH, + tokens.SWETH, + holders.ETH, + '1000000000000000000', + SwapSide.SELL, + dexKey, + contractMethod, + network, + provider, + ); + }); + + it(`${contractMethod} - WETH -> wdivWETH`, async () => { + await testE2E( + tokens.WETH, + tokens.SWETH, + holders.WETH, + '1000000000000000000', + SwapSide.SELL, + dexKey, + contractMethod, + network, + provider, + ); + }); + }); +}); diff --git a/src/dex/diva/diva.ts b/src/dex/diva/diva.ts index c119f1876..77158d630 100644 --- a/src/dex/diva/diva.ts +++ b/src/dex/diva/diva.ts @@ -14,10 +14,9 @@ import { import { IDex } from '../idex'; import DIVETH_ABI from '../../abi/divETH.json'; import WDIVETH_ABI from '../../abi/wdivETH.json'; -import { ETHER_ADDRESS, Network } from '../../constants'; +import { ETHER_ADDRESS, Network, NULL_ADDRESS } from '../../constants'; import { IDexHelper } from '../../dex-helper'; import { SimpleExchange } from '../simple-exchange'; -import { BI_POWS } from '../../bigint-constants'; import { AsyncOrSync } from 'ts-essentials'; import { getDexKeysWithNetwork, isETHAddress } from '../../utils'; import { WethFunctions } from '../weth/types'; @@ -25,10 +24,7 @@ import * as CALLDATA_GAS_COST from '../../calldata-gas-cost'; import _ from 'lodash'; import { DivaConfig, Adapters } from './config'; import { DivETHEventPool } from './diveth-pool'; - -export enum swETHFunctions { - deposit = 'deposit', -} +import { BI_POWS } from '../../bigint-constants'; export type SwellData = {}; export type SwellParams = {}; @@ -48,7 +44,7 @@ export class Diva logger: Logger; public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = - getDexKeysWithNetwork(_.pick(DivaConfig, ['Swell'])); + getDexKeysWithNetwork(_.pick(DivaConfig, ['Diva'])); constructor( protected network: Network, @@ -57,7 +53,7 @@ export class Diva protected config = DivaConfig[dexKey][network], protected adapters = Adapters[network], ) { - super(dexHelper, 'Diva'); + super(dexHelper, dexKey); this.network = dexHelper.config.data.network; this.divETHInterface = new Interface(DIVETH_ABI as JsonFragment[]); @@ -78,10 +74,6 @@ export class Diva await this.divETHEventPool.initialize(blockNumber); } - getPoolIdentifierKey(): string { - return `${ETHER_ADDRESS}_${this.divETHEventPool}`.toLowerCase(); - } - isEligibleSwap( srcToken: Token | string, destToken: Token | string, @@ -108,7 +100,7 @@ export class Diva side: SwapSide, ) { if (!this.isEligibleSwap(srcToken, destToken, side)) { - throw new Error('Only eth -> divETH/wdivETH and divETH <-> wdivETH swaps are supported'); + throw new Error('Only ETH/wETH -> divETH/wdivETH and divETH <-> wdivETH swaps are supported'); } } @@ -118,9 +110,9 @@ export class Diva side: SwapSide, blockNumber: number, ): Promise { - return this.isEligibleSwap(srcToken, destToken, side) - ? [this.getPoolIdentifierKey()] - : []; + if(!this.isEligibleSwap(srcToken, destToken, side)) return []; + + return [`${ETHER_ADDRESS}_${destToken}`.toLowerCase()]; } async getPricesVolume( @@ -136,25 +128,23 @@ export class Diva if (!this.isEligibleSwap(srcToken, destToken, side)) return null; if (this.divETHEventPool.getState(blockNumber) === null) return null; - return null; - - // const unitIn = BI_POWS[18]; - // const unitOut = this.divETHEventPool.getPrice(blockNumber, unitIn); - // const amountsOut = amountsIn.map(amountIn => - // this.divETHEventPool.getPrice(blockNumber, amountIn), - // ); - // - // return [ - // { - // prices: amountsOut, - // unit: unitOut, - // data: {}, - // exchange: this.dexKey, - // poolIdentifier: this.getPoolIdentifierKey(), - // gasCost: 120_000, - // poolAddresses: [this.swETHAddress], - // }, - // ]; + const unitIn = BI_POWS[18]; + const unitOut = this.divETHEventPool.convertToShares(blockNumber, unitIn); + const amountsOut = amountsIn.map(amountIn => + this.divETHEventPool.convertToShares(blockNumber, amountIn), + ); + + return [ + { + prices: amountsOut, + unit: unitOut, + data: {}, + exchange: this.dexKey, + poolIdentifier: `${ETHER_ADDRESS}_${destToken.address}`.toLowerCase(), + gasCost: 120_000, + poolAddresses: [destToken.address], + }, + ]; } getAdapterParam( @@ -168,7 +158,7 @@ export class Diva this.assertEligibility(srcToken, destToken, side); return { - targetExchange: this.divETHAddress, // not used contract side + targetExchange: NULL_ADDRESS, payload: '0x', networkFee: '0', }; @@ -189,7 +179,7 @@ export class Diva const values = []; if (this.isWETH(srcToken)) { - // note: apparently ERC20 ABI contains wETH fns (deposit() and withdraw()) + // note: ERC20 ABI contains wETH functions const wethUnwrapData = this.erc20Interface.encodeFunctionData( WethFunctions.withdraw, [srcAmount], @@ -200,7 +190,7 @@ export class Diva } const swapData = this.divETHInterface.encodeFunctionData( - swETHFunctions.deposit, + 'deposit', [], ); diff --git a/src/dex/index.ts b/src/dex/index.ts index 4b8efa8a4..df89e6038 100644 --- a/src/dex/index.ts +++ b/src/dex/index.ts @@ -87,6 +87,7 @@ import { Wombat } from './wombat/wombat'; import { Swell } from './swell/swell'; import { PharaohV1 } from './solidly/forks-override/pharaohV1'; import { EtherFi } from './etherfi'; +import { Diva } from './diva/diva'; const LegacyDexes = [ CurveV2, @@ -170,6 +171,7 @@ const Dexes = [ Wombat, Swell, PharaohV1, + Diva, ]; export type LegacyDexConstructor = new (dexHelper: IDexHelper) => IDexTxBuilder< diff --git a/tests/constants-e2e.ts b/tests/constants-e2e.ts index cf972fd67..44808d265 100644 --- a/tests/constants-e2e.ts +++ b/tests/constants-e2e.ts @@ -351,6 +351,15 @@ export const Tokens: { address: '0xe07f9d810a48ab5c3c914ba3ca53af14e4491e8a', decimals: 18, }, + // todo: update with mainnet addresses (these are on Sepolia) + divETH: { + address: '0x78Cb3BE3ee9C7aD14967aD10F9D2baFA79F2DC94', + decimals: 18, + }, + wdivETH: { + address: '0x3F806A22dc942934c66DeF5FF8eC45a89A5aF454', + decimals: 18, + }, }, [Network.ROPSTEN]: { DAI: { @@ -1152,6 +1161,9 @@ export const Holders: { SDEX: '0xB0470cF15B22a6A32c49a7C20E3821B944A76058', frxETH: '0x9df2322bdAEC46627100C999E6dDdD27837fec6e', USDe: '0x74e6c48e667d698a4cf90665b6960a5bae39e603', + // todo: update with real holders + divETH: '0x75C94990D2Ad92D8da4E0a238d872D09ec16706E', + wdivETH: '0x75C94990D2Ad92D8da4E0a238d872D09ec16706E', }, [Network.ROPSTEN]: { ETH: '0x43262A12d8610AA70C15DbaeAC321d51613c9071',