Skip to content

Commit

Permalink
feat: generic-rfq: support smart contract wallet
Browse files Browse the repository at this point in the history
Resolves BACK-874.
  • Loading branch information
Louis-Amas authored and skypper committed Mar 2, 2023
1 parent 12dbe50 commit d3d27a9
Show file tree
Hide file tree
Showing 9 changed files with 147 additions and 37 deletions.
3 changes: 2 additions & 1 deletion src/abi/ERC1271.abi.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
],
"outputs": [
{
"type": "bool"
"type": "bool",
"name": ""
}
]
}
Expand Down
3 changes: 2 additions & 1 deletion src/dex/generic-rfq/generic-rfq.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ export class GenericRFQ extends ParaSwapLimitOrders {
this.rateFetcher = new RateFetcher(dexHelper, config, dexKey, this.logger);
}

initializePricing(blockNumber: number): void {
async initializePricing(blockNumber: number): Promise<void> {
await this.rateFetcher.initialize();
if (!this.dexHelper.config.isSlave) {
this.rateFetcher.start();
}
Expand Down
21 changes: 21 additions & 0 deletions src/dex/generic-rfq/rate-fetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ import {
import { genericRFQAuthHttp } from './security';

const GET_FIRM_RATE_TIMEOUT_MS = 2000;
import {
createERC1271Contract,
ERC1271Contract,
} from '../../lib/erc1271-utils';
import { isContractAddress } from '../../utils';

export const reversePrice = (price: PriceAndAmountBigNumber) =>
[
Expand All @@ -58,6 +63,8 @@ export class RateFetcher {
secet: RFQSecret,
) => (options: RequestConfig) => RequestConfig;

private verifierContract?: ERC1271Contract;

constructor(
private dexHelper: IDexHelper,
private config: RFQConfig,
Expand Down Expand Up @@ -143,6 +150,19 @@ export class RateFetcher {
}
}

async initialize() {
const isContract = await isContractAddress(
this.dexHelper.web3Provider,
this.config.maker,
);
if (isContract) {
this.verifierContract = createERC1271Contract(
this.dexHelper.web3Provider,
this.config.maker,
);
}
}

start() {
this.tokensFetcher.startPolling();
this.pairsFetcher.startPolling();
Expand Down Expand Up @@ -397,6 +417,7 @@ export class RateFetcher {
this.dexHelper.config.data.augustusRFQAddress,
this.dexHelper.multiWrapper,
firmRateResp.order,
this.verifierContract,
);

return {
Expand Down
35 changes: 24 additions & 11 deletions src/dex/generic-rfq/utils.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,44 @@
import { ethers } from 'ethers';
import { Address, Token } from '../../types';
import { Address } from '../../types';
import { getBalances } from '../../lib/tokens/balancer-fetcher';
import {
AssetType,
DEFAULT_ID_ERC20,
DEFAULT_ID_ERC20_AS_STRING,
} from '../../lib/tokens/types';
import { calculateOrderHash } from '../paraswap-limit-orders/utils';
import { AugustusOrderWithStringAndSignature, RFQPayload } from './types';
import { AugustusOrderWithStringAndSignature } from './types';
import { MultiWrapper } from '../../lib/multi-wrapper';
import { Network } from '../../constants';
import { ERC1271Contract } from '../../lib/erc1271-utils';

export const checkOrder = async (
network: Network,
augustusRFQAddress: Address,
multiWrapper: MultiWrapper,
order: AugustusOrderWithStringAndSignature,
verifierContract?: ERC1271Contract,
) => {
const hash = calculateOrderHash(network, order, augustusRFQAddress);

if (verifierContract) {
const isValid = await verifierContract.methods
.isValidSignature(hash, order.signature)
.call();

if (!isValid) {
throw new Error(`signature is invalid`);
}
} else {
const recovered = ethers.utils
.recoverAddress(hash, order.signature)
.toLowerCase();

if (recovered !== order.maker.toLowerCase()) {
throw new Error(`signature is invalid`);
}
}

const balances = await getBalances(multiWrapper, [
{
owner: order.maker,
Expand Down Expand Up @@ -49,13 +71,4 @@ export const checkOrder = async (
`maker does not have enough allowance (request ${makerAmountBigInt} value ${takerBalance}`,
);
}
const hash = calculateOrderHash(network, order, augustusRFQAddress);

const recovered = ethers.utils
.recoverAddress(hash, order.signature)
.toLowerCase();

if (recovered !== order.maker.toLowerCase()) {
throw new Error(`signature is invalid`);
}
};
50 changes: 27 additions & 23 deletions src/dex/generic-rfq/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,26 +171,30 @@ const stringStartWithHex0x = (
return value;
};

export const orderWithSignatureValidator = joi.object({
nonceAndMeta: joi.string().custom(stringPositiveBigIntValidator),
expiry: joi.number().min(0),
maker: addressSchema.required(),
taker: addressSchema.required(),
makerAsset: addressSchema.required(),
takerAsset: addressSchema.required(),
makerAmount: joi
.string()
.min(1)
.custom(stringPositiveBigIntValidator)
.required(),
takerAmount: joi
.string()
.min(1)
.custom(stringPositiveBigIntValidator)
.required(),
signature: joi.string().custom(stringStartWithHex0x),
});

export const firmRateResponseValidator = joi.object({
order: orderWithSignatureValidator.required(),
});
export const orderWithSignatureValidator = joi
.object({
nonceAndMeta: joi.string().custom(stringPositiveBigIntValidator),
expiry: joi.number().min(0),
maker: addressSchema.required(),
taker: addressSchema.required(),
makerAsset: addressSchema.required(),
takerAsset: addressSchema.required(),
makerAmount: joi
.string()
.min(1)
.custom(stringPositiveBigIntValidator)
.required(),
takerAmount: joi
.string()
.min(1)
.custom(stringPositiveBigIntValidator)
.required(),
signature: joi.string().custom(stringStartWithHex0x),
})
.unknown(true);

export const firmRateResponseValidator = joi
.object({
order: orderWithSignatureValidator.required(),
})
.unknown(true);
24 changes: 24 additions & 0 deletions src/lib/erc1271-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import Web3 from 'web3';
import type { Contract, ContractSendMethod } from 'web3-eth-contract';
import { AbiItem } from 'web3-utils';
import ERC1271ABI from '../abi/ERC1271.abi.json';

export interface ERC1271Contract extends Contract {
methods: {
isValidSignature(
message: string,
signature: string,
): Omit<ContractSendMethod, 'call'> & {
call: (
...params: Parameters<ContractSendMethod['call']>
) => Promise<boolean>;
};
};
}

export const createERC1271Contract = (
web3: Web3,
addr: string,
): ERC1271Contract => {
return new web3.eth.Contract(ERC1271ABI as AbiItem[], addr);
};
38 changes: 38 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import { BI_MAX_UINT256, BI_POWS } from './bigint-constants';
import { ETHER_ADDRESS, Network } from './constants';
import { DexConfigMap, Logger, TransferFeeParams } from './types';
import _ from 'lodash';
import { MultiResult } from './lib/multi-wrapper';
import { Contract } from 'web3-eth-contract';
import Web3 from 'web3';

export const isETHAddress = (address: string) =>
address.toLowerCase() === ETHER_ADDRESS.toLowerCase();
Expand Down Expand Up @@ -348,3 +351,38 @@ export const isDestTokenTransferFeeToBeExchanged = (

export const isTruthy = <T>(x: T | undefined | null | '' | false | 0): x is T =>
!!x;

type MultiCallParams = {
target: any;
callData: any;
};

type BlockAndTryAggregateResult = {
blockNumber: number;
results: MultiResult<any>[];
};

export const blockAndTryAggregate = async (
mandatory: boolean,
multi: Contract,
calls: MultiCallParams[],
blockNumber: number | 'latest',
): Promise<BlockAndTryAggregateResult> => {
const results = await multi.methods
.tryBlockAndAggregate(mandatory, calls)
.call(undefined, blockNumber);

return {
blockNumber: Number(results.blockNumber),
results: results.returnData.map((res: any) => ({
returnData: res.returnData,
success: res.success,
})),
};
};

export const isContractAddress = async (web3: Web3, addr: string) => {
const contractCode = await web3.eth.getCode(addr);

return contractCode !== '0x';
};
8 changes: 7 additions & 1 deletion tests/constants-e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {
SmartToken,
balancesFn,
allowedFn,
_balancesFn,
_allowancesFn,
} from '../tests/smart-tokens';
import { Address } from '../src/types';
import { ETHER_ADDRESS, Network } from '../src/constants';
Expand Down Expand Up @@ -299,6 +301,8 @@ export const Tokens: {
USDC: {
address: '0x2791bca1f2de4661ed88a30c99a7a9449aa84174',
decimals: 6,
addBalance: _balancesFn,
addAllowance: _allowancesFn,
},
POPS: {
address: '0xa92A1576D11dB45c53be71d59245ac97ce0d8147',
Expand All @@ -311,6 +315,8 @@ export const Tokens: {
WMATIC: {
address: '0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270',
decimals: 18,
addBalance: balanceOfFn,
addAllowance: allowanceFn,
},
AMWMATIC: {
address: '0x8dF3aad3a84da6b69A4DA8aeC3eA40d9091B2Ac4',
Expand Down Expand Up @@ -716,7 +722,7 @@ export const Tokens: {
WBTC: {
address: '0x2f2a2543b76a4166549f7aab2e75bef0aefc5b0f',
decimals: 8,
}
},
},
[Network.OPTIMISM]: {
DAI: {
Expand Down
2 changes: 2 additions & 0 deletions tests/smart-tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ const constructAddBAllowanceFn = (varName: string): AddAllowanceFn => {

export const balanceOfFn = constructAddBalanceFn('balanceOf');
export const balancesFn = constructAddBalanceFn('balances');
export const _balancesFn = constructAddBalanceFn('_balances');
export const allowanceFn = constructAddBAllowanceFn('allowance');
export const _allowancesFn = constructAddBAllowanceFn('_allowances');
export const allowedFn = constructAddBAllowanceFn('allowed');

export type SmartTokenParams = Token & {
Expand Down

0 comments on commit d3d27a9

Please sign in to comment.