-
Notifications
You must be signed in to change notification settings - Fork 102
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(twap): twap order creation services (#2504)
* feat(twap): twap order creation services * chore: twap creation docs * test: fix tests * test: fix tests
- Loading branch information
Showing
14 changed files
with
566 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
[ | ||
{ | ||
"inputs": [ | ||
{ | ||
"components": [ | ||
{ | ||
"internalType": "contract IConditionalOrder", | ||
"name": "handler", | ||
"type": "address" | ||
}, | ||
{ | ||
"internalType": "bytes32", | ||
"name": "salt", | ||
"type": "bytes32" | ||
}, | ||
{ | ||
"internalType": "bytes", | ||
"name": "staticInput", | ||
"type": "bytes" | ||
} | ||
], | ||
"internalType": "struct IConditionalOrder.ConditionalOrderParams", | ||
"name": "params", | ||
"type": "tuple" | ||
}, | ||
{ | ||
"internalType": "bool", | ||
"name": "dispatch", | ||
"type": "bool" | ||
} | ||
], | ||
"name": "create", | ||
"outputs": [], | ||
"stateMutability": "nonpayable", | ||
"type": "function" | ||
} | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
[ | ||
{ | ||
"inputs": [ | ||
{ | ||
"internalType": "address", | ||
"name": "_defaultFallbackHandler", | ||
"type": "address" | ||
} | ||
], | ||
"stateMutability": "nonpayable", | ||
"type": "constructor" | ||
}, | ||
{ | ||
"anonymous": false, | ||
"inputs": [ | ||
{ | ||
"indexed": true, | ||
"internalType": "contract Safe", | ||
"name": "safe", | ||
"type": "address" | ||
}, | ||
{ | ||
"indexed": false, | ||
"internalType": "bytes4", | ||
"name": "selector", | ||
"type": "bytes4" | ||
}, | ||
{ | ||
"indexed": false, | ||
"internalType": "contract IFallbackMethod", | ||
"name": "handler", | ||
"type": "address" | ||
} | ||
], | ||
"name": "AddedSafeMethod", | ||
"type": "event" | ||
}, | ||
{ | ||
"anonymous": false, | ||
"inputs": [ | ||
{ | ||
"indexed": true, | ||
"internalType": "contract Safe", | ||
"name": "safe", | ||
"type": "address" | ||
}, | ||
{ | ||
"indexed": false, | ||
"internalType": "address", | ||
"name": "oldHandler", | ||
"type": "address" | ||
}, | ||
{ | ||
"indexed": false, | ||
"internalType": "address", | ||
"name": "newHandler", | ||
"type": "address" | ||
} | ||
], | ||
"name": "ChangedDefaultFallbackHandler", | ||
"type": "event" | ||
}, | ||
{ | ||
"anonymous": false, | ||
"inputs": [ | ||
{ | ||
"indexed": true, | ||
"internalType": "contract Safe", | ||
"name": "safe", | ||
"type": "address" | ||
}, | ||
{ | ||
"indexed": false, | ||
"internalType": "bytes4", | ||
"name": "selector", | ||
"type": "bytes4" | ||
}, | ||
{ | ||
"indexed": false, | ||
"internalType": "contract IFallbackMethod", | ||
"name": "oldHandler", | ||
"type": "address" | ||
}, | ||
{ | ||
"indexed": false, | ||
"internalType": "contract IFallbackMethod", | ||
"name": "newHandler", | ||
"type": "address" | ||
} | ||
], | ||
"name": "ChangedSafeMethod", | ||
"type": "event" | ||
}, | ||
{ | ||
"anonymous": false, | ||
"inputs": [ | ||
{ | ||
"indexed": true, | ||
"internalType": "contract Safe", | ||
"name": "safe", | ||
"type": "address" | ||
}, | ||
{ | ||
"indexed": false, | ||
"internalType": "bytes4", | ||
"name": "selector", | ||
"type": "bytes4" | ||
} | ||
], | ||
"name": "RemovedSafeMethod", | ||
"type": "event" | ||
}, | ||
{ | ||
"stateMutability": "nonpayable", | ||
"type": "fallback" | ||
}, | ||
{ | ||
"inputs": [ | ||
{ | ||
"internalType": "address", | ||
"name": "newHandler", | ||
"type": "address" | ||
} | ||
], | ||
"name": "setDefaultFallbackHandler", | ||
"outputs": [], | ||
"stateMutability": "nonpayable", | ||
"type": "function" | ||
}, | ||
{ | ||
"inputs": [ | ||
{ | ||
"internalType": "bytes4", | ||
"name": "selector", | ||
"type": "bytes4" | ||
}, | ||
{ | ||
"internalType": "contract IFallbackMethod", | ||
"name": "newHandler", | ||
"type": "address" | ||
} | ||
], | ||
"name": "setSafeMethod", | ||
"outputs": [], | ||
"stateMutability": "nonpayable", | ||
"type": "function" | ||
} | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { SupportedChainId } from '@cowprotocol/cow-sdk' | ||
|
||
export const COMPOSABLE_COW_ADDRESS: Record<SupportedChainId, string> = { | ||
1: 'TODO', | ||
100: 'TODO', | ||
5: '0xa31b99bd44528c7bae9e1f675d810ae13b0e29aa', | ||
} |
10 changes: 10 additions & 0 deletions
10
src/modules/advancedOrders/hooks/useComposableCowContract.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { ComposableCoW } from 'abis/types' | ||
import { useWalletInfo } from 'modules/wallet' | ||
import COMPOSABLE_COW_ABI from 'abis/ComposableCoW.json' | ||
import { useContract } from 'legacy/hooks/useContract' | ||
import { COMPOSABLE_COW_ADDRESS } from '../const' | ||
|
||
export function useComposableCowContract(): ComposableCoW | null { | ||
const { chainId } = useWalletInfo() | ||
return useContract<ComposableCoW>(chainId ? COMPOSABLE_COW_ADDRESS[chainId] : undefined, COMPOSABLE_COW_ABI, true) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# TWAP orders | ||
|
||
## Twap order creation | ||
|
||
The process follows common pattern: collect context -> execute logic | ||
|
||
![twap-creation](./docs/twap-creation.drawio.svg) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { SupportedChainId } from '@cowprotocol/cow-sdk' | ||
|
||
export const TWAP_ORDER_STRUCT = | ||
'tuple(address sellToken,address buyToken,address receiver,uint256 partSellAmount,uint256 minPartLimit,uint256 t0,uint256 n,uint256 t,uint256 span)' | ||
|
||
export const TWAP_HANDLER_ADDRESS: Record<SupportedChainId, string> = { | ||
1: 'TODO', | ||
100: 'TODO', | ||
5: '0xa12d770028d7072b80baeb6a1df962374fd13d9a', | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { useComposableCowContract } from 'modules/advancedOrders/hooks/useComposableCowContract' | ||
import { useNeedsApproval } from 'common/hooks/useNeedsApproval' | ||
import { useTokenContract } from 'legacy/hooks/useContract' | ||
import { ComposableCoW } from 'abis/types' | ||
import { Erc20 } from 'legacy/abis/types' | ||
import { CurrencyAmount, Token } from '@uniswap/sdk-core' | ||
import { Nullish } from 'types' | ||
import { useWalletInfo } from 'modules/wallet' | ||
import { useSafeAppsSdk } from 'modules/wallet/web3-react/hooks/useSafeAppsSdk' | ||
import { SupportedChainId } from '@cowprotocol/cow-sdk' | ||
import SafeAppsSDK from '@safe-global/safe-apps-sdk' | ||
import { useTradeSpenderAddress } from 'common/hooks/useTradeSpenderAddress' | ||
|
||
export interface TwapOrderCreationContext { | ||
chainId: SupportedChainId | ||
safeAppsSdk: SafeAppsSDK | ||
composableCowContract: ComposableCoW | ||
needsApproval: boolean | ||
spender: string | ||
erc20Contract: Erc20 | ||
} | ||
|
||
export function useTwapOrderCreationContext( | ||
inputAmount: Nullish<CurrencyAmount<Token>> | ||
): TwapOrderCreationContext | null { | ||
const { chainId } = useWalletInfo() | ||
const safeAppsSdk = useSafeAppsSdk() | ||
const composableCowContract = useComposableCowContract() | ||
const needsApproval = useNeedsApproval(inputAmount) | ||
const erc20Contract = useTokenContract(inputAmount?.currency.address) | ||
const spender = useTradeSpenderAddress() | ||
|
||
if (!composableCowContract || !erc20Contract || !chainId || !safeAppsSdk || !spender) return null | ||
|
||
return { chainId, safeAppsSdk, composableCowContract, erc20Contract, needsApproval, spender } | ||
} |
67 changes: 67 additions & 0 deletions
67
src/modules/twap/services/__snapshots__/createTwapOrderTxs.test.ts.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`Create TWAP order When sell token is NOT approved, then should generate approval and creation transactions 1`] = ` | ||
Array [ | ||
"create", | ||
Array [ | ||
Object { | ||
"handler": "0xa12d770028d7072b80baeb6a1df962374fd13d9a", | ||
"salt": "0x00000000000000000000000000000000000000000000000000000015c90b9b2a", | ||
"staticInput": "0x00000000000000000000000091056d4a53e1faa1a84306d4deaec71085394bc8000000000000000000000000b4fbf271143f4fbf7b91a5ded31805e42b2208d6000000000000000000000000b4fbf271143f4fbf7b91a5ded31805e42b2208d600000000000000000000000000000000000000000000000000000007c2d24d55000000000000000000000000000000000000000000000000000000000001046a00000000000000000000000000000000000000000000000000000000646b782c000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000002580000000000000000000000000000000000000000000000000000000000000000", | ||
}, | ||
true, | ||
], | ||
] | ||
`; | ||
|
||
exports[`Create TWAP order When sell token is NOT approved, then should generate approval and creation transactions 2`] = ` | ||
Array [ | ||
"approve", | ||
Array [ | ||
"0xB4FBF271143F4FBf7B91A5ded31805e42b222222", | ||
"100000000000", | ||
], | ||
] | ||
`; | ||
|
||
exports[`Create TWAP order When sell token is NOT approved, then should generate approval and creation transactions 3`] = ` | ||
Array [ | ||
Object { | ||
"data": "0xAPPROVE_TX_DATA", | ||
"operation": 0, | ||
"to": "0x91056D4A53E1faa1A84306D4deAEc71085394bC8", | ||
"value": "0", | ||
}, | ||
Object { | ||
"data": "0xCREATE_COW_TX_DATA", | ||
"operation": 0, | ||
"to": "0xa31b99bd44528c7bae9e1f675d810ae13b0e29aa", | ||
"value": "0", | ||
}, | ||
] | ||
`; | ||
|
||
exports[`Create TWAP order When sell token is approved, then should generate only creation transaction 1`] = ` | ||
Array [ | ||
"create", | ||
Array [ | ||
Object { | ||
"handler": "0xa12d770028d7072b80baeb6a1df962374fd13d9a", | ||
"salt": "0x00000000000000000000000000000000000000000000000000000015c90b9b2a", | ||
"staticInput": "0x00000000000000000000000091056d4a53e1faa1a84306d4deaec71085394bc8000000000000000000000000b4fbf271143f4fbf7b91a5ded31805e42b2208d6000000000000000000000000b4fbf271143f4fbf7b91a5ded31805e42b2208d600000000000000000000000000000000000000000000000000000007c2d24d55000000000000000000000000000000000000000000000000000000000001046a00000000000000000000000000000000000000000000000000000000646b782c000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000002580000000000000000000000000000000000000000000000000000000000000000", | ||
}, | ||
true, | ||
], | ||
] | ||
`; | ||
|
||
exports[`Create TWAP order When sell token is approved, then should generate only creation transaction 2`] = ` | ||
Array [ | ||
Object { | ||
"data": "0xCREATE_COW_TX_DATA", | ||
"operation": 0, | ||
"to": "0xa31b99bd44528c7bae9e1f675d810ae13b0e29aa", | ||
"value": "0", | ||
}, | ||
] | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import { createTwapOrderTxs } from './createTwapOrderTxs' | ||
import { TWAPOrder } from '../types' | ||
import { CurrencyAmount } from '@uniswap/sdk-core' | ||
import { COW } from '../../../legacy/constants/tokens' | ||
import { SupportedChainId } from '@cowprotocol/cow-sdk' | ||
import { WETH_GOERLI } from '../../../legacy/utils/goerli/constants' | ||
import { TwapOrderCreationContext } from '../hooks/useTwapOrderCreationContext' | ||
|
||
const order: TWAPOrder = { | ||
sellAmount: CurrencyAmount.fromRawAmount(COW[SupportedChainId.GOERLI], 100_000_000_000), | ||
buyAmount: CurrencyAmount.fromRawAmount(WETH_GOERLI, 200_000), | ||
receiver: '0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6', | ||
numOfParts: 3, | ||
startTime: 1684764716, | ||
timeInterval: 600, | ||
span: 0, | ||
} | ||
|
||
const CREATE_COW_TX_DATA = '0xCREATE_COW_TX_DATA' | ||
const APPROVE_TX_DATA = '0xAPPROVE_TX_DATA' | ||
|
||
describe('Create TWAP order', () => { | ||
let context: TwapOrderCreationContext | ||
let createCowFn: jest.Mock | ||
let approveFn: jest.Mock | ||
|
||
beforeEach(() => { | ||
jest.spyOn(Date, 'now').mockImplementation(() => 1497076708000) | ||
|
||
createCowFn = jest.fn().mockReturnValue(CREATE_COW_TX_DATA) | ||
approveFn = jest.fn().mockReturnValue(APPROVE_TX_DATA) | ||
|
||
context = { | ||
chainId: SupportedChainId.GOERLI, | ||
safeAppsSdk: null as any, | ||
composableCowContract: { interface: { encodeFunctionData: createCowFn } } as any, | ||
needsApproval: false, | ||
spender: '0xB4FBF271143F4FBf7B91A5ded31805e42b222222', | ||
erc20Contract: { interface: { encodeFunctionData: approveFn } } as any, | ||
} | ||
}) | ||
|
||
it('When sell token is approved, then should generate only creation transaction', () => { | ||
const result = createTwapOrderTxs(order, { ...context, needsApproval: false }) | ||
|
||
expect(createCowFn).toHaveBeenCalledTimes(1) | ||
expect(createCowFn.mock.calls[0]).toMatchSnapshot() | ||
|
||
expect(result.length).toBe(1) | ||
expect(result).toMatchSnapshot() | ||
}) | ||
|
||
it('When sell token is NOT approved, then should generate approval and creation transactions', () => { | ||
const result = createTwapOrderTxs(order, { ...context, needsApproval: true }) | ||
|
||
expect(createCowFn).toHaveBeenCalledTimes(1) | ||
expect(createCowFn.mock.calls[0]).toMatchSnapshot() | ||
|
||
expect(approveFn).toHaveBeenCalledTimes(1) | ||
expect(approveFn.mock.calls[0]).toMatchSnapshot() | ||
|
||
expect(result.length).toBe(2) | ||
expect(result).toMatchSnapshot() | ||
}) | ||
}) |
Oops, something went wrong.