Skip to content

Commit

Permalink
Merge pull request #91 from Aperture-Finance/estimate_gas
Browse files Browse the repository at this point in the history
Add ability to estimate gas for relayer-initiated rebalance and reinvest txs
  • Loading branch information
aperture11 authored Dec 28, 2023
2 parents 2e16b42 + 185e389 commit 99b3f80
Show file tree
Hide file tree
Showing 4 changed files with 244 additions and 1 deletion.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@aperture_finance/uniswap-v3-automation-sdk",
"version": "1.12.0",
"version": "1.13.0",
"description": "SDK for Aperture's Uniswap V3 automation platform",
"author": "Aperture Finance <[email protected]>",
"license": "MIT",
Expand Down
104 changes: 104 additions & 0 deletions test/hardhat/hardhat.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ import {
PositionDetails,
checkPositionApprovalStatus,
computeOperatorApprovalSlot,
estimateRebalanceGas,
estimateReinvestGas,
generateAccessList,
generatePriceConditionFromTokenValueProportion,
generateTypedDataForPermit,
Expand Down Expand Up @@ -124,6 +126,108 @@ async function resetFork(testClient: TestClient) {
});
}

describe('Estimate gas tests', function () {
async function estimateRebalanceGasWithFrom(from: Address | undefined) {
const blockNumber = 17975698n;
const publicClient = createPublicClient({
chain: mainnet,
transport: http(
`https://mainnet.infura.io/v3/${process.env.INFURA_API_KEY}`,
),
});
const token0 = WBTC_ADDRESS;
const token1 = WETH_ADDRESS;
const fee = FeeAmount.MEDIUM;
const amount0Desired = 100000000n;
const amount1Desired = 1000000000000000000n;
const pool = await getPool(
token0,
token1,
fee,
chainId,
undefined,
blockNumber,
);
const mintParams = {
token0: token0 as Address,
token1: token1 as Address,
fee,
tickLower: nearestUsableTick(
pool.tickCurrent - 10 * pool.tickSpacing,
pool.tickSpacing,
),
tickUpper: nearestUsableTick(
pool.tickCurrent + 10 * pool.tickSpacing,
pool.tickSpacing,
),
amount0Desired,
amount1Desired,
amount0Min: BigInt(0),
amount1Min: BigInt(0),
recipient: eoa as Address,
deadline: BigInt(Math.floor(Date.now() / 1000 + 60 * 30)),
};
const gas = await estimateRebalanceGas(
chainId,
publicClient,
from,
eoa,
mintParams,
4n,
undefined,
undefined,
blockNumber,
);
return gas;
}

async function estimateReinvestGasWithFrom(from: Address | undefined) {
const blockNumber = 17975698n;
const publicClient = createPublicClient({
chain: mainnet,
transport: http(
`https://mainnet.infura.io/v3/${process.env.INFURA_API_KEY}`,
),
});
const amount0Desired = 100000n;
const amount1Desired = 1000000000000000n;
const gas = await estimateReinvestGas(
chainId,
publicClient,
from,
eoa,
4n,
BigInt(Math.floor(Date.now() / 1000 + 60 * 30)),
amount0Desired,
amount1Desired,
BigInt(0),
'0x',
blockNumber,
);
return gas;
}

it('Test estimateRebalanceGas with owner', async function () {
const gas = await estimateRebalanceGasWithFrom(eoa);
expect(gas).to.equal(775010n);
});

it('Test estimateRebalanceGas with whale', async function () {
const gas = await estimateRebalanceGasWithFrom(undefined);
expect(gas).to.equal(777510n);
});

it('Test estimateReinvestGas with owner', async function () {
const gas = await estimateReinvestGasWithFrom(eoa);
expect(gas).to.equal(528206n);
});

it('Test estimateReinvestGas with whale', async function () {
const gas = await estimateReinvestGasWithFrom(undefined);
expect(gas).to.equal(528206n);
});
});

describe('State overrides tests', function () {
it('Test computeOperatorApprovalSlot', async function () {
const testClient = await hre.viem.getTestClient();
Expand Down
78 changes: 78 additions & 0 deletions viem/automan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
getContract,
hexToSignature,
} from 'viem';
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts';

import { getChainInfo } from '../chain';
import { ApertureSupportedChainId, PermitInfo } from '../interfaces';
Expand All @@ -20,10 +21,12 @@ import {
} from '../typechain-types';
import { GetAbiFunctionParamsTypes } from './generics';
import {
estimateGasWithOverrides,
getERC20Overrides,
getNPMApprovalOverrides,
staticCallWithOverrides,
tryStaticCallWithOverrides,
updateIsControllerOverrides,
} from './overrides';

export type AutomanActionName =
Expand Down Expand Up @@ -447,3 +450,78 @@ export async function simulateRebalance(
functionName: 'rebalance',
});
}

export async function estimateRebalanceGas(
chainId: ApertureSupportedChainId,
publicClient: PublicClient,
from: Address | undefined,
owner: Address,
mintParams: MintParams,
tokenId: bigint,
feeBips = BigInt(0),
swapData: Hex = '0x',
blockNumber?: bigint,
): Promise<bigint> {
checkTicks(mintParams);
const data = getAutomanRebalanceCalldata(
mintParams,
tokenId,
feeBips,
undefined,
swapData,
);
const overrides = getNPMApprovalOverrides(chainId, owner);
if (from === undefined) {
const privateKey = generatePrivateKey();
const account = privateKeyToAccount(privateKey);
from = account.address;
}
updateIsControllerOverrides(overrides, chainId, from);
return await estimateGasWithOverrides(
from,
getChainInfo(chainId).aperture_uniswap_v3_automan,
data,
overrides,
publicClient,
blockNumber,
);
}

export async function estimateReinvestGas(
chainId: ApertureSupportedChainId,
publicClient: PublicClient,
from: Address | undefined,
owner: Address,
tokenId: bigint,
deadline: bigint,
amount0Min = BigInt(0),
amount1Min = BigInt(0),
feeBips = BigInt(0),
swapData: Hex = '0x',
blockNumber?: bigint,
): Promise<bigint> {
const data = getAutomanReinvestCalldata(
tokenId,
deadline,
amount0Min,
amount1Min,
feeBips,
undefined,
swapData,
);
const overrides = getNPMApprovalOverrides(chainId, owner);
if (from === undefined) {
const privateKey = generatePrivateKey();
const account = privateKeyToAccount(privateKey);
from = account.address;
}
updateIsControllerOverrides(overrides, chainId, from);
return await estimateGasWithOverrides(
from,
getChainInfo(chainId).aperture_uniswap_v3_automan,
data,
overrides,
publicClient,
blockNumber,
);
}
61 changes: 61 additions & 0 deletions viem/overrides.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
RpcTransactionRequest,
encodeAbiParameters,
encodeFunctionData,
hexToBigInt,
keccak256,
parseAbiParameters,
toHex,
Expand Down Expand Up @@ -49,6 +50,20 @@ export function computeOperatorApprovalSlot(
);
}

/**
* Compute the storage slot for the isController in UniV3Automan.
* @param from The address of controller.
* @returns The storage slot.
*/
export function computeIsControllerSlot(from: Address): Hex {
return keccak256(
encodeAbiParameters(parseAbiParameters('address, bytes32'), [
from,
encodeAbiParameters(parseAbiParameters('uint256'), [2n]),
]),
);
}

export function getNPMApprovalOverrides(
chainId: ApertureSupportedChainId,
owner: Address,
Expand All @@ -67,6 +82,22 @@ export function getNPMApprovalOverrides(
};
}

export function updateIsControllerOverrides(
overrides: StateOverrides,
chainId: ApertureSupportedChainId,
from: Address,
) {
const { aperture_uniswap_v3_automan } = getChainInfo(chainId);
overrides[aperture_uniswap_v3_automan] = {
stateDiff: {
[computeIsControllerSlot(from)]: encodeAbiParameters(
parseAbiParameters('bool'),
[true],
),
},
};
}

export function getAutomanWhitelistOverrides(
chainId: ApertureSupportedChainId,
routerToWhitelist: Address,
Expand Down Expand Up @@ -217,6 +248,36 @@ export async function staticCallWithOverrides(
})) as Hex;
}

/**
* Estimate Gas of a contract call with the given state overrides.
* @param tx The transaction request.
* @param overrides The state overrides.
* @param publicClient A JSON RPC provider that supports `eth_estimateGas` with state overrides.
* @param blockNumber Optional block number to use for the call.
*/
export async function estimateGasWithOverrides(
from: Address,
to: Address,
data: Hex,
overrides: StateOverrides,
publicClient: PublicClient,
blockNumber?: bigint,
): Promise<bigint> {
const tx = {
from,
to,
data,
};
return hexToBigInt(
(await publicClient.request({
method: 'eth_estimateGas',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
params: [tx, blockNumber ? toHex(blockNumber) : 'latest', overrides],
})) as Hex,
);
}

/**
* Try to call a contract with the given state overrides. If the call fails, fall back to a regular call.
* @param from The sender address.
Expand Down

0 comments on commit 99b3f80

Please sign in to comment.