Skip to content

Commit

Permalink
feat: initial resolver processing
Browse files Browse the repository at this point in the history
  • Loading branch information
marthendalnunes committed Dec 11, 2024
1 parent 56797dd commit 4792158
Show file tree
Hide file tree
Showing 13 changed files with 1,824 additions and 42 deletions.
4 changes: 4 additions & 0 deletions apps/api-delegations/src/env.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { createEnv } from '@t3-oss/env-core';
import { z } from 'zod';
import 'dotenv/config';
import { isHex } from 'viem';

export const env = createEnv({
server: {
DELEGATIONS_DATABASE_URL: z.string().url(),
RESOLVER_PRIVATE_KEY: z.string().refine(isHex),
RPC_URL_BASE: z.string().url(),
RPC_URL_BASE_SEPOLIA: z.string().url(),
},
runtimeEnv: process.env,
emptyStringAsUndefined: true,
Expand Down
108 changes: 108 additions & 0 deletions apps/api-delegations/src/processing/limit-order/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import type { Delegation, DelegationExecution } from 'universal-types';
import {
decodeEnforcerERC20TransferAmount,
encodeDelegation,
encodeSingleExecution,
getErc20TransferAmountEnforcerFromDelegation,
getExternalHookEnforcerFromDelegation,
} from './utils.js';
import { getResolverWalletClient } from '../../resolver/resolver-wallet-client.js';
import type { ValidChain } from 'universal-data';
import {
SINGLE_EXECUTION_MODE,
aaveV3PoolAbi,
delegationManagerAbi,
universalDeployments,
} from 'universal-data';
import { encodeFunctionData, type Address, type Hex, erc20Abi } from 'viem';
import { multicallAbi } from 'universal-data';

const AAVE_V3_POOL_BASE = '0xA238Dd80C259a72e81d7e4664a9801593F98d1c5';

function getDepositAaveV3HookData({
amount,
delegator,
token,
}: { amount: bigint; delegator: Address; token: Address }): Hex {
return encodeFunctionData({
abi: multicallAbi,

functionName: 'multicall',
args: [
[
// Approves the token to the Aave Pool
{
target: token,
value: 0n,
callData: encodeFunctionData({
abi: erc20Abi,
functionName: 'approve',
args: [AAVE_V3_POOL_BASE, amount],
}),
},
// Deposits the token to the Aave Pool on behalf of the delegator
{
target: AAVE_V3_POOL_BASE,
value: 0n,
callData: encodeFunctionData({
abi: aaveV3PoolAbi,
functionName: 'supply',
args: [token, amount, delegator, 0],
}),
},
],
],
});
}

export async function processLimitOrderDelegation({
chainId,
delegation,
}: { chainId: ValidChain['id']; delegation: Delegation }) {
const { erc20TransferAmountEnforcer } =
getErc20TransferAmountEnforcerFromDelegation(delegation);
const { token, amount } = decodeEnforcerERC20TransferAmount(
erc20TransferAmountEnforcer.terms,
);

const { externalHookEnforcer, index: externalHookEnforcerIndex } =
getExternalHookEnforcerFromDelegation(delegation);

delegation.caveats[externalHookEnforcerIndex] = {
...externalHookEnforcer,
// Update the args of the external hook enforcer to include the data for the Aave V3 deposit
args: getDepositAaveV3HookData({
amount,
token,
delegator: delegation.delegator,
}),
};

// Get resolver wallet client
const resolverWalletClient = getResolverWalletClient(chainId);

// Set the delegation execution to transfer the delegator tokens to the multicall contract
const execution: DelegationExecution = {
value: 0n,
target: token,
calldata: encodeFunctionData({
abi: erc20Abi,
functionName: 'transfer',
args: [universalDeployments.Multicall, amount],
}),
};

const permissionContexts = [encodeDelegation(delegation)];
const executionCallData = [encodeSingleExecution(execution)];
const executionModes = SINGLE_EXECUTION_MODE;

// Redeem the delegation
const txHash = await resolverWalletClient.writeContract({
address: universalDeployments.DelegationManager,
abi: delegationManagerAbi,
functionName: 'redeemDelegations',
args: [permissionContexts, executionModes, executionCallData],
});

return txHash;
}
140 changes: 140 additions & 0 deletions apps/api-delegations/src/processing/limit-order/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { universalDeployments } from 'universal-data';
import type { Delegation, DelegationExecution } from 'universal-types';
import {
type Address,
type Hex,
encodePacked,
hexToBigInt,
parseUnits,
sliceHex,
encodeAbiParameters,
} from 'viem';

export function encodeEnforcerERC20TransferAmount(data: {
token: Address;
amount: string;
decimals: number;
}) {
return encodePacked(
['address', 'uint256'],
[data.token, parseUnits(data.amount, data.decimals)],
);
}

const NoEnforcerFoundError = new Error('No ERC20TransferAmountEnforcer found');

export function decodeEnforcerERC20TransferAmount(data: Hex) {
// Addresses are 20 bytes, uint256 is 32 bytes
const addressSize = 20;
const uint256Size = 32;

// Decode `token` (first 20 bytes)
const token = sliceHex(data, 0, addressSize) as Address;

// Decode `amount` (next 32 bytes)
const amountHex = sliceHex(data, addressSize, addressSize + uint256Size);
const amount = hexToBigInt(amountHex);

return {
token,
amount,
};
}

export function getErc20TransferAmountEnforcerFromDelegation(
delegation: Delegation,
) {
const index = delegation.caveats.findIndex(
({ enforcer }) =>
enforcer.toLowerCase() ===
universalDeployments.ERC20TransferAmountEnforcer.toLowerCase(),
);
if (index === -1) {
throw NoEnforcerFoundError;
}

const erc20TransferAmountEnforcer = delegation.caveats[index];

if (!erc20TransferAmountEnforcer) {
throw NoEnforcerFoundError;
}

return { erc20TransferAmountEnforcer, index };
}

export function getExternalHookEnforcerFromDelegation(delegation: Delegation) {
const index = delegation.caveats.findIndex(
({ enforcer }) =>
enforcer.toLowerCase() ===
universalDeployments.ExternalHookEnforcer.toLowerCase(),
);
if (index === -1) {
throw NoEnforcerFoundError;
}

const externalHookEnforcer = delegation.caveats[index];

if (!externalHookEnforcer) {
throw NoEnforcerFoundError;
}

return { externalHookEnforcer, index };
}

// Typescript implementation of: https://github.com/erc7579/erc7579-implementation/blob/main/src/lib/ExecutionLib.sol#L51-L62
export function encodeSingleExecution({
calldata,
target,
value,
}: DelegationExecution): Hex {
return encodePacked(
['address', 'uint256', 'bytes'],
[target, value, calldata],
);
}

export function encodeDelegation(delegation: Delegation): Hex {
return encodeAbiParameters(
[
{
name: '_delegation',
type: 'tuple[]',
internalType: 'struct Delegation',
components: [
{
name: 'delegate',
type: 'address',
internalType: 'address',
},
{
name: 'delegator',
type: 'address',
internalType: 'address',
},
{
name: 'authority',
type: 'bytes32',
internalType: 'bytes32',
},
{
name: 'caveats',
type: 'tuple[]',
internalType: 'struct Caveat[]',
components: [
{
name: 'enforcer',
type: 'address',
internalType: 'address',
},
{ name: 'terms', type: 'bytes', internalType: 'bytes' },
{ name: 'args', type: 'bytes', internalType: 'bytes' },
],
},
{ name: 'salt', type: 'uint256', internalType: 'uint256' },
{ name: 'signature', type: 'bytes', internalType: 'bytes' },
],
},
],
[[delegation]],
);
}
10 changes: 7 additions & 3 deletions apps/api-delegations/src/processing/process-delegation.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import type { Delegation } from 'universal-types';
import { getDelegationType } from './utils/get-delegation-type.js';
import { processLimitOrderDelegation } from './process-limit-order-delegation.js';
import { processLimitOrderDelegation } from './limit-order/index.js';
import type { ValidChain } from 'universal-data';

export async function processDelegation(delegation: Delegation) {
export async function processDelegation({
chainId,
delegation,
}: { chainId: ValidChain['id']; delegation: Delegation }) {
const delegationType = getDelegationType(delegation);

if (delegationType === 'LimitOrder') {
return processLimitOrderDelegation(delegation);
await processLimitOrderDelegation({ chainId, delegation });
}
}

This file was deleted.

30 changes: 30 additions & 0 deletions apps/api-delegations/src/resolver/resolver-wallet-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type { ValidChain } from 'universal-data';
import { createWalletClient, http } from 'viem';
import { base, baseSepolia } from 'viem/chains';
import { env } from '../env.js';
import { privateKeyToAccount } from 'viem/accounts';

const account = privateKeyToAccount(env.RESOLVER_PRIVATE_KEY);

const resolverWalletClientBase = createWalletClient({
account,
chain: base,
transport: http(env.RPC_URL_BASE),
});

const resolverWalletClientBaseSepolia = createWalletClient({
account,
chain: baseSepolia,
transport: http(env.RPC_URL_BASE_SEPOLIA),
});

export function getResolverWalletClient(chainId: ValidChain['id']) {
switch (chainId) {
case base.id:
return resolverWalletClientBase;
case baseSepolia.id:
return resolverWalletClientBaseSepolia;
default:
throw new Error(`Invalid chainId: ${chainId}`);
}
}
Loading

0 comments on commit 4792158

Please sign in to comment.