Skip to content

Commit

Permalink
feat: add Permit2 pull token logics
Browse files Browse the repository at this point in the history
  • Loading branch information
Jeff-CCH committed Nov 22, 2023
1 parent 2d3ffc0 commit 8cccf10
Show file tree
Hide file tree
Showing 10 changed files with 166 additions and 11 deletions.
5 changes: 5 additions & 0 deletions .changeset/young-geese-joke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@protocolink/logics': patch
---

add Permit2 pull token logics
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"dependencies": {
"@paraswap/sdk": "^6.2.2",
"@protocolink/common": "^0.3.4",
"@protocolink/core": "^0.4.3",
"@protocolink/core": "^0.4.4",
"@types/lodash": "^4.14.195",
"@uniswap/sdk-core": "^3.2.6",
"@uniswap/token-lists": "^1.0.0-beta.31",
Expand Down
5 changes: 3 additions & 2 deletions src/logics/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ export * as aavev3 from './aave-v3';
export * as balancerv2 from './balancer-v2';
export * as compoundv2 from './compound-v2';
export * as compoundv3 from './compound-v3';
export * as openoceanv2 from './openocean-v2';
export * as paraswapv5 from './paraswap-v5';
export * as permit2 from './permit2';
export * as radiantv2 from './radiant-v2';
export * as syncswap from './syncswap';
export * as uniswapv3 from './uniswap-v3';
export * as utility from './utility';
export * as radiantv2 from './radiant-v2';
export * as openoceanv2 from './openocean-v2';
3 changes: 3 additions & 0 deletions src/logics/permit2/configs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import * as core from '@protocolink/core';

export const supportedChainIds = Object.keys(core.contractAddressMap).map((chainId) => Number(chainId));
2 changes: 2 additions & 0 deletions src/logics/permit2/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './configs';
export * from './logic.pull-token';
37 changes: 37 additions & 0 deletions src/logics/permit2/logic.pull-token.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { LogicTestCase } from 'test/types';
import { PullTokenLogic, PullTokenLogicFields } from './logic.pull-token';
import * as common from '@protocolink/common';
import { constants, utils } from 'ethers';
import * as core from '@protocolink/core';
import { expect } from 'chai';
import { mainnetTokens } from '@protocolink/test-helpers';

describe('Permit2 PullTokenLogic', function () {
const chainId = common.ChainId.mainnet;
const logic = new PullTokenLogic(chainId);

context('Test build', function () {
const routerKit = new core.RouterKit(chainId);
const account = '0xaAaAaAaaAaAaAaaAaAAAAAAAAaaaAaAaAaaAaaAa';
const iface = routerKit.permit2Iface;

const testCases: LogicTestCase<PullTokenLogicFields>[] = [
{ fields: { input: new common.TokenAmount(mainnetTokens.WETH, '1') } },
];

testCases.forEach(({ fields }, i) => {
it(`case ${i + 1}`, async function () {
const routerLogic = await logic.build(fields, { account });
const sig = routerLogic.data.substring(0, 10);
const permit2Address = await routerKit.getPermit2Address();

expect(routerLogic.to).to.eq(permit2Address);
expect(utils.isBytesLike(routerLogic.data)).to.be.true;
expect(sig).to.eq(iface.getSighash('transferFrom(address,address,uint160,address)'));
expect(routerLogic.inputs).to.deep.eq([]);
expect(routerLogic.approveTo).to.eq(constants.AddressZero);
expect(routerLogic.callback).to.eq(constants.AddressZero);
});
});
});
});
26 changes: 26 additions & 0 deletions src/logics/permit2/logic.pull-token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import * as core from '@protocolink/core';
import { supportedChainIds } from './configs';

export type PullTokenLogicFields = core.TokenInFields;

export type PullTokenLogicOptions = Pick<core.GlobalOptions, 'account'>;

@core.LogicDefinitionDecorator()
export class PullTokenLogic extends core.Logic implements core.LogicBuilderInterface {
static readonly supportedChainIds = supportedChainIds;

async build(fields: PullTokenLogicFields, options: PullTokenLogicOptions) {
const { input } = fields;
const { account } = options;
const userAgent = await this.calcAgent(account);
const to = await this.getPermit2Address();
const data = this.permit2Iface.encodeFunctionData('transferFrom(address,address,uint160,address)', [
account,
userAgent,
input.amountWei,
input.token.address,
]);

return core.newLogic({ to, data });
}
}
65 changes: 65 additions & 0 deletions test/logics/permit2/pull-token.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { claimToken, getChainId, mainnetTokens, snapshotAndRevertEach } from '@protocolink/test-helpers';
import * as common from '@protocolink/common';
import * as core from '@protocolink/core';
import { expect } from 'chai';
import hre from 'hardhat';
import * as permit2 from 'src/logics/permit2';
import * as utils from 'test/utils';

describe('mainnet: Test Permit2 PullToken Logic', function () {
let chainId: number;
let user: SignerWithAddress;
let routerKit: core.RouterKit;
let agent: string;

before(async function () {
chainId = await getChainId();
[, user] = await hre.ethers.getSigners();
routerKit = new core.RouterKit(chainId);
agent = await routerKit.calcAgent(user.address);

await claimToken(chainId, user.address, mainnetTokens.USDC, '100');
await claimToken(chainId, user.address, mainnetTokens.WETH, '100');
});

snapshotAndRevertEach();

const testCases = [
{
fields: { input: new common.TokenAmount(mainnetTokens.USDC, '1') },
},
{
fields: { input: new common.TokenAmount(mainnetTokens.WETH, '1') },
},
];

testCases.forEach(({ fields }, i) => {
it(`case ${i + 1}`, async function () {
// 1. build tokensReturn
const input = fields.input;
const permit2PullTokenLogic = new permit2.PullTokenLogic(chainId);
const tokensReturn: string[] = [];
const funds = new common.TokenAmounts(fields.input);

// 2. build router logics
const routerLogics: core.DataType.LogicStruct[] = [];
routerLogics.push(await permit2PullTokenLogic.build(fields, { account: user.address }));

// 3. get router permit2 datas
const permit2Datas = await utils.approvePermit2AndGetPermit2Datas(chainId, user, funds.erc20);

// 4. send router tx
const transactionRequest = routerKit.buildExecuteTransactionRequest({
permit2Datas,
routerLogics,
tokensReturn,
value: funds.native?.amountWei ?? 0,
});

await expect(user.sendTransaction(transactionRequest)).to.not.be.reverted;
await expect(user.address).to.changeBalance(input.token, -input.amount);
await expect(agent).to.changeBalance(input.token, input.amount);
});
});
});
24 changes: 20 additions & 4 deletions test/utils/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,26 @@ export function calcRequiredAmountByBalanceBps(input: common.TokenAmount, balanc
}

export async function getRouterPermit2Datas(chainId: number, user: SignerWithAddress, inputs: common.TokenAmounts) {
const permit2Datas: string[] = [];
if (!inputs.isEmpty) {
// 1. approve permit2 and get permit2 permit call data
const permit2DatasWithoutTransferFrom = await approvePermit2AndGetPermit2Datas(chainId, user, inputs);
permit2Datas.push(...permit2DatasWithoutTransferFrom);

// 2. get permit2 transferFrom data
const router = new core.RouterKit(chainId, hre.ethers.provider);
const transferFromCallData = await router.encodePermit2TransferFrom(user.address, inputs);
permit2Datas.push(transferFromCallData);
}

return permit2Datas;
}

export async function approvePermit2AndGetPermit2Datas(
chainId: number,
user: SignerWithAddress,
inputs: common.TokenAmounts
) {
const permit2Datas: string[] = [];
if (!inputs.isEmpty) {
const router = new core.RouterKit(chainId, hre.ethers.provider);
Expand All @@ -32,10 +52,6 @@ export async function getRouterPermit2Datas(chainId: number, user: SignerWithAdd
const permitCallData = router.encodePermit2Permit(user.address, permitData.values, permitSig);
permit2Datas.push(permitCallData);
}

// 3. get permit2 transferFrom data
const transferFromCallData = await router.encodePermit2TransferFrom(user.address, inputs);
permit2Datas.push(transferFromCallData);
}

return permit2Datas;
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1033,10 +1033,10 @@
type-fest "^3.12.0"
zksync-web3 "^0.14.3"

"@protocolink/core@^0.4.3":
version "0.4.3"
resolved "https://registry.yarnpkg.com/@protocolink/core/-/core-0.4.3.tgz#4c2533acf62092d1e76b3e7ac7c2f9c69ae3e355"
integrity sha512-7AyR5Mu/aAfP9rDXkpc70gK29JtMDXDdzRfTXWKf7OisXXqPUuOuC6D0s2zbZr5DCd/IzMURZOSQ4VjfGsspfQ==
"@protocolink/core@^0.4.4":
version "0.4.4"
resolved "https://registry.yarnpkg.com/@protocolink/core/-/core-0.4.4.tgz#f47e4e1897f9cd5dc82276762b5ccbb70398a8f7"
integrity sha512-NiSe3YFqqKrtyInyRjE2e3VBqB75SreMbHzh2HA381hVESClpP8lH7O/B+6hNcDDn8zDujMb1hE2TDPzGymZ9w==
dependencies:
"@protocolink/common" "^0.3.4"
"@uniswap/permit2-sdk" "^1.2.0"
Expand Down

0 comments on commit 8cccf10

Please sign in to comment.