Skip to content

Commit

Permalink
feat(twap): twap order creation services (#2504)
Browse files Browse the repository at this point in the history
* feat(twap): twap order creation services

* chore: twap creation docs

* test: fix tests

* test: fix tests
  • Loading branch information
shoom3301 authored May 24, 2023
1 parent e1ce2c7 commit 6c73de7
Show file tree
Hide file tree
Showing 14 changed files with 566 additions and 0 deletions.
37 changes: 37 additions & 0 deletions src/abis/ComposableCoW.json
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"
}
]
148 changes: 148 additions & 0 deletions src/abis/ExtensibleFallbackHandler.json
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"
}
]
7 changes: 7 additions & 0 deletions src/modules/advancedOrders/const.ts
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 src/modules/advancedOrders/hooks/useComposableCowContract.ts
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)
}
7 changes: 7 additions & 0 deletions src/modules/twap/README.md
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)
10 changes: 10 additions & 0 deletions src/modules/twap/const.ts
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',
}
4 changes: 4 additions & 0 deletions src/modules/twap/docs/twap-creation.drawio.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
36 changes: 36 additions & 0 deletions src/modules/twap/hooks/useTwapOrderCreationContext.ts
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 }
}
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",
},
]
`;
65 changes: 65 additions & 0 deletions src/modules/twap/services/createTwapOrderTxs.test.ts
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()
})
})
Loading

0 comments on commit 6c73de7

Please sign in to comment.