Skip to content

Commit

Permalink
feat: withdraw and swap (#555)
Browse files Browse the repository at this point in the history
* feat: withdraw and swap contract call

* feat: add export

* feat: new swap and withdraw with populate transaction

* feat: changed swap to switch in withdrawAndSwitch
  • Loading branch information
JoaquinBattilana authored Aug 22, 2023
1 parent 5e44c45 commit ca6c230
Show file tree
Hide file tree
Showing 9 changed files with 1,182 additions and 4 deletions.
1 change: 1 addition & 0 deletions packages/contract-helpers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"prebuild": "npm run clean",
"build": "cd ../.. && tsc -p packages/contract-helpers/tsconfig.json && tsc -p packages/contract-helpers/tsconfig.json --module commonjs --outDir ./packages/contract-helpers/dist/cjs",
"test": "cd ../.. && yarn test packages/contract-helpers",
"test:watch": "cd ../.. && yarn test --watch packages/contract-helpers",
"cover": "cd ../.. && yarn cover packages/contract-helpers",
"commit": "cd ../.. && yarn commit",
"prepublishOnly": "yarn build"
Expand Down
1 change: 1 addition & 0 deletions packages/contract-helpers/src/commons/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ export enum ProtocolAction {
claimRewards = 'claimRewards',
claimRewardsAndStake = 'claimRewardsAndStake',
setUsageAsCollateral = 'setUsageAsCollateral',
withdrawAndSwitch = 'withdrawAndSwitch',
}

export enum GovernanceVote {
Expand Down
8 changes: 4 additions & 4 deletions packages/contract-helpers/src/commons/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,6 @@ export const gasLimitRecommendations: GasRecommendationType = {
limit: '350000',
recommended: '350000',
},
[ProtocolAction.stake]: {
limit: '350000',
recommended: '350000',
},
[ProtocolAction.stakeWithPermit]: {
limit: '400000',
recommended: '400000',
Expand All @@ -130,6 +126,10 @@ export const gasLimitRecommendations: GasRecommendationType = {
limit: '138000',
recommended: '138000',
},
[ProtocolAction.withdrawAndSwitch]: {
limit: '1000000',
recommended: '1000000',
},
};

export const mintAmountsPerToken: Record<string, string> = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,31 @@ export function LiquiditySwapValidator(
};
}

export function WithdrawAndSwitchValidator(
target: any,
propertyName: string,
descriptor: TypedPropertyDescriptor<any>,
): any {
const method = descriptor.value;
descriptor.value = function () {
// @ts-expect-error todo: check why this ignore is needed
if (!utils.isAddress(this.withdrawAndSwitchAdapterAddress)) {
console.error(
`[WithdrawAndSwitchValidator] You need to pass valid addresses`,
);
return [];
}

isEthAddressValidator(target, propertyName, arguments);

amountGtThan0Validator(target, propertyName, arguments);

amountGtThan0OrMinus1(target, propertyName, arguments);

return method.apply(this, arguments);
};
}

export function RepayWithCollateralValidator(
target: any,
propertyName: string,
Expand Down
1 change: 1 addition & 0 deletions packages/contract-helpers/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export * from './gho';
export * from './v3-migration-contract';
export * from './erc20-2612';
export * from './paraswap-debtSwitch-contract';
export * from './paraswap-withdrawAndSwitchAdapter-contract';

// commons
export * from './commons/types';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { BytesLike, PopulatedTransaction, providers } from 'ethers';
import BaseService from '../commons/BaseService';
import { PermitSignature, tEthereumAddress } from '../commons/types';
import { WithdrawAndSwitchValidator } from '../commons/validators/methodValidators';
import {
isEthAddress,
isPositiveAmount,
} from '../commons/validators/paramValidators';
import { augustusFromAmountOffsetFromCalldata } from '../paraswap-liquiditySwapAdapter-contract';
import {
ParaSwapWithdrawSwapAdapter,
ParaSwapWithdrawSwapAdapterInterface,
} from './typechain/ParaSwapWithdrawSwapAdapter';
import { ParaSwapWithdrawSwapAdapter__factory } from './typechain/ParaSwapWithdrawSwapAdapter__factory';

export type WithdrawAndSwitchMethodType = {
user: tEthereumAddress;
assetToSwitchFrom: tEthereumAddress;
assetToSwitchTo: tEthereumAddress;
amountToSwitch: string; // wei
minAmountToReceive: string; // wei
permitParams: PermitSignature;
switchCallData: BytesLike;
augustus: tEthereumAddress;
switchAll: boolean;
};

export interface WithdrawSwitchAdapterInterface {
withdrawAndSwitch: (
args: WithdrawAndSwitchMethodType,
) => PopulatedTransaction;
}

export class WithdrawAndSwitchAdapterService
extends BaseService<ParaSwapWithdrawSwapAdapter>
implements WithdrawSwitchAdapterInterface
{
readonly withdrawAndSwitchAdapterAddress: string;
readonly contractInterface: ParaSwapWithdrawSwapAdapterInterface;

constructor(
provider: providers.Provider,
withdrawSwitchAdapterAddress?: string,
) {
super(provider, ParaSwapWithdrawSwapAdapter__factory);

this.withdrawAndSwitchAdapterAddress = withdrawSwitchAdapterAddress ?? '';

this.contractInterface =
ParaSwapWithdrawSwapAdapter__factory.createInterface();

this.withdrawAndSwitch = this.withdrawAndSwitch.bind(this);
}

@WithdrawAndSwitchValidator
public withdrawAndSwitch(
@isEthAddress('user')
@isEthAddress('assetToSwitchFrom')
@isEthAddress('assetToSwitchTo')
@isEthAddress('augustus')
@isPositiveAmount('amountToSwitch')
@isPositiveAmount('minAmountToReceive')
{
user,
assetToSwitchFrom,
assetToSwitchTo,
amountToSwitch,
minAmountToReceive,
permitParams,
augustus,
switchCallData,
switchAll,
}: WithdrawAndSwitchMethodType,
): PopulatedTransaction {
const actionTx: PopulatedTransaction = {};

const txData = this.contractInterface.encodeFunctionData(
'withdrawAndSwap',
[
assetToSwitchFrom,
assetToSwitchTo,
amountToSwitch,
minAmountToReceive,
switchAll
? augustusFromAmountOffsetFromCalldata(switchCallData as string)
: 0,
switchCallData,
augustus,
permitParams,
],
);

actionTx.to = this.withdrawAndSwitchAdapterAddress;
actionTx.data = txData;
actionTx.from = user;

return actionTx;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { BigNumber, PopulatedTransaction, providers } from 'ethers';
import { WithdrawAndSwitchAdapterService } from './index';

jest.mock('../commons/gasStation', () => {
return {
__esModule: true,
estimateGasByNetwork: jest
.fn()
.mockImplementation(async () => Promise.resolve(BigNumber.from(1))),
estimateGas: jest.fn(async () => Promise.resolve(BigNumber.from(1))),
};
});

describe('WithdrawAndSwitchAdapterService', () => {
const withdrawAndSwitchAdapterAddress =
'0x0000000000000000000000000000000000000001';
describe('Initialization', () => {
const provider = new providers.JsonRpcProvider();
it('Expects to initialize with full params', () => {
expect(
() =>
new WithdrawAndSwitchAdapterService(
provider,
withdrawAndSwitchAdapterAddress,
),
).not.toThrow();
});
it('Expects to initialize without withdraw and swap adapter address', () => {
expect(() => new WithdrawAndSwitchAdapterService(provider)).not.toThrow();
});
});
describe('withdrawAndSwitch', () => {
const user = '0x0000000000000000000000000000000000000002';
const assetToSwitchFrom = '0x0000000000000000000000000000000000000003';
const assetToSwitchTo = '0x0000000000000000000000000000000000000004';
const amountToSwitch = '1000000';
const minAmountToReceive = '10000000';
const augustus = '0x0000000000000000000000000000000000000006';
const permitParams = {
deadline: '0',
r: '0x0000000000000000000000000000000000000000000000000000000000000000',
s: '0x0000000000000000000000000000000000000000000000000000000000000000',
v: 0,
amount: amountToSwitch,
};

const txCalldata =
'0xda8567c80000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c5990000000000000000000000007fc66500c84a76ad7e9c93437bfc5ac33e2ddae900000000000000000000000000000000000000000000000000000000119027b700000000000000000000000000000000000000000000001e483c86fa843f6c2000000000000000000000000000000000000000000000000000000000000000440000000000000000000000000000000000000000000000000000000000000180000000000000000000000000def171fe48cf0115b1d80b88dc8eab59176fee5700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000116446c67b6d00000000000000000000000000000000000000000000000000000000000000200000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c59900000000000000000000000000000000000000000000000000000000119027b700000000000000000000000000000000000000000000001dfec636122d1008df00000000000000000000000000000000000000000000001e4c566f818d31d01300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000006161766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001120000000000000000000000000000000000000000000000000000000006177abb9ae2ba6c0362c11ec8f12233bf85851b3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000004a000000000000000000000000000000000000000000000000000000000000015180000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000007fc66500c84a76ad7e9c93437bfc5ac33e2ddae900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000003a0430bf7cd2633af111ce3204db4b0990857a6f000000000000000000000000000000000000000000000000000000000000271000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25eff000000000000000000000000000000000000000000000000000000000000271000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000007fc66500c84a76ad7e9c93437bfc5ac33e2ddae90000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c5990000000000000000000000000000000000000000000000108e47a6846afd000000000000000000000000000000000000000000000000000000000000099433f80000000000000000000000000000006daea1723962647b7e189d311d757fb793000000000000000000000000def171fe48cf0115b1d80b88dc8eab59176fee57000000000000000000000000100cec21fa2a0bdc21f770ec06e885e7c52a18680000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006177abb93d1d355dba3f70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000001c558d406f7449cc936da0231093b069fcb8a42e3a2f0f78f0497515f8d795a29c3a57485920bb65c42c9f5884af63388d493b5cd0bcd2a6eed4fff7173982f4f800000000000000000000000000000000000000000000000000000000000011f80000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000420000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000003a0430bf7cd2633af111ce3204db4b0990857a6f000000000000000000000000000000000000000000000000000000000000271000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25eff000000000000000000000000000000000000000000000000000000000000271000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c5990000000000000000000000000000000000000000000000011abcf02276ffa00000000000000000000000000000000000000000000000000000000000082b007c000000000000000000000000b3c839dbde6b96d37c56ee4f9dad3390d49310aa000000000000000000000000def171fe48cf0115b1d80b88dc8eab59176fee57000000000000000000000000100cec21fa2a0bdc21f770ec06e885e7c52a18680000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006177abd60000000000000000000000000000000000000000000000000000017cbb773bf00000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000001bc072f450f48f66263bd53e801840fbf545c7aa5aac969d568f87eb830b66f36907cba9fd12582a9a96f23d1396da6c94d46fa4bc0ab851972246cd117659949f0000000000000000000000007fc66500c84a76ad7e9c93437bfc5ac33e2ddae900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000003a0430bf7cd2633af111ce3204db4b0990857a6f0000000000000000000000000000000000000000000000000000000000002710000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000002c00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25eff00000000000000000000000000000000000000000000000000000000000009c400000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000007fc66500c84a76ad7e9c93437bfc5ac33e2ddae9000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000386598f16ac87200000000000000000000000000000000000000000000000000046a1698a248c022a0000000000000000000000000000006daea1723962647b7e189d311d757fb793000000000000000000000000def171fe48cf0115b1d80b88dc8eab59176fee57000000000000000000000000100cec21fa2a0bdc21f770ec06e885e7c52a18680000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006177abb902d4eee4d7c128000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000001c41623d640c08c3543660e464261260aed59bfba57c22b00326533525bd011953350326cee2a506e6daf1d0d8780eca139ec48cab2280c6e0bf1a1efe72b0ad880000000000000000000000000000000000000000000000000000000000000001000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25eff0000000000000000000000000000000000000000000000000000000000001d4c00000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000007fc66500c84a76ad7e9c93437bfc5ac33e2ddae9000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000a91c3e5350e240000000000000000000000000000000000000000000000000000d41a7b6e826d0000000000000000000000000000b3c839dbde6b96d37c56ee4f9dad3390d49310aa000000000000000000000000def171fe48cf0115b1d80b88dc8eab59176fee57000000000000000000000000100cec21fa2a0bdc21f770ec06e885e7c52a18680000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006177abd60000000000000000000000000000000000000000000000000000017cbb773bf00000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000001b842ca8317744091cfd12b2d2e9de4c17618cf2e19d5f0eecb68e3691b078542307aae0aeff4ae756ca20d0ec1dff2ec0a6cfdc595dd3a43d6b8a76f630c4b2f9000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000';

const provider: providers.Provider = new providers.JsonRpcProvider();
jest
.spyOn(provider, 'getGasPrice')
.mockImplementation(async () => Promise.resolve(BigNumber.from(1)));

const switchInstance = new WithdrawAndSwitchAdapterService(
provider,
withdrawAndSwitchAdapterAddress,
);

afterEach(() => {
jest.clearAllMocks();
});

it('Expects the tx object when sending all correct params', async () => {
const txObj: PopulatedTransaction = switchInstance.withdrawAndSwitch({
user,
assetToSwitchFrom,
assetToSwitchTo,
amountToSwitch,
minAmountToReceive,
permitParams,
switchCallData: txCalldata,
switchAll: true,
augustus,
});

expect(txObj.from).toEqual(user);
expect(txObj.to).toEqual(withdrawAndSwitchAdapterAddress);
// function selector
expect(txObj.data?.substring(0, 10)).toEqual('0x5fd73e07');

const txObjRepayAllFalse: PopulatedTransaction =
switchInstance.withdrawAndSwitch({
user,
assetToSwitchFrom,
assetToSwitchTo,
amountToSwitch,
minAmountToReceive,
permitParams,
switchCallData: txCalldata,
switchAll: false,
augustus,
});

expect(txObjRepayAllFalse.from).toEqual(user);
expect(txObjRepayAllFalse.to).toEqual(withdrawAndSwitchAdapterAddress);
// function selector
expect(txObjRepayAllFalse.data?.substring(0, 10)).toEqual('0x5fd73e07');
});
});
});
Loading

0 comments on commit ca6c230

Please sign in to comment.