Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/fill OTC Order with Permit #186

Merged
merged 17 commits into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
216 changes: 216 additions & 0 deletions src/methods/common/orders/encoding.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
import { splitSignature } from './signature';

type EncodeEIP_2612PermitFunctionInput = {
permitSignature: string;
owner: string;
spender: string;
value: string | bigint;
deadline: string | number | bigint;
};

// encoding params for Token.permit() Permit1 function
export function encodeEIP_2612PermitFunctionInput({
owner,
spender,
value,
deadline,
permitSignature,
}: EncodeEIP_2612PermitFunctionInput): string {
const { v, r, s } = splitSignature(permitSignature);

const encodedOwner = encodeAddress(owner);
const encodedSpender = encodeAddress(spender);
const encodedValue = encodeUint256(value);
const encodedDeadline = encodeUint256(deadline.toString());
const encodedV = encodeUint8(v);
const encodedR = encodeBytes32(r);
const encodedS = encodeBytes32(s);

// Concatenate all encoded values, stripping the "0x" prefix from each (except the first one)
return (
'0x' +
[
encodedOwner,
encodedSpender,
encodedValue,
encodedDeadline,
encodedV,
encodedR,
encodedS,
]
.map((val) => val.slice(2)) // Remove "0x" prefix from each encoded value
.join('') // Concatenate the values
);
}

type EncodeDAIlikePermitFunctionInput = {
permitSignature: string;
holder: string;
spender: string;
nonce: number | bigint | string;
expiry: number | bigint | string;
};

// encoding params for DAIlike.permit() function
export function encodeDAIlikePermitFunctionInput({
permitSignature,
holder,
spender,
nonce,
expiry,
}: EncodeDAIlikePermitFunctionInput): string {
const { v, r, s } = splitSignature(permitSignature);

const encodedHolder = encodeAddress(holder);
const encodedSpender = encodeAddress(spender);
const encodedNonce = encodeUint256(nonce.toString());
const encodedExpiry = encodeUint256(expiry.toString());
const encodedV = encodeUint8(v);
const encodedR = encodeBytes32(r);
const encodedS = encodeBytes32(s);

// Concatenate all encoded values, stripping the "0x" prefix from each (except the first one)
return (
'0x' +
[
encodedHolder,
encodedSpender,
encodedNonce,
encodedExpiry,
encodeBool(true), //allowed=true
encodedV,
encodedR,
encodedS,
]
.map((val) => val.slice(2)) // Remove "0x" prefix from each encoded value
.join('') // Concatenate the values
);
}

// encode an address (20 bytes) into 32 bytes
export function encodeAddress(address: string): string {
const strippedAddress = address.replace(/^0x/, ''); // Remove "0x" prefix
return '0x' + strippedAddress.toLowerCase().padStart(64, '0');
}

// encode a uint256 value
export function encodeUint256(value: string | bigint): string {
const bn = BigInt(value);
return '0x' + bn.toString(16).padStart(64, '0');
}

// encode a uint8 value
export function encodeUint8(value: number | bigint): string {
return '0x' + value.toString(16).padStart(64, '0');
}

// encode a bytes32 value
export function encodeBytes32(value: string): string {
const strippedValue = value.replace(/^0x/, ''); // Remove "0x" prefix
return '0x' + strippedValue.padStart(64, '0').toLowerCase();
}

//encode a boolean
export function encodeBool(value: boolean): string {
const encodedValue = value ? '1' : '0';
// padded to 32 bytes
return '0x' + encodedValue.padStart(64, '0');
}

/*
const EIP_2612_PERMIT_ABI = [
{
constant: false,
inputs: [
{
name: 'owner',
type: 'address',
},
{
name: 'spender',
type: 'address',
},
{
name: 'value',
type: 'uint256',
},
{
name: 'deadline',
type: 'uint256',
},
{
name: 'v',
type: 'uint8',
},
{
name: 'r',
type: 'bytes32',
},
{
name: 's',
type: 'bytes32',
},
],
name: 'permit',
outputs: [],
payable: false,
stateMutability: 'nonpayable',
type: 'function',
},
];
*/

/*
const DAI_EIP_2612_PERMIT_ABI = [
{
constant: false,
inputs: [
{
internalType: 'address',
name: 'holder',
type: 'address',
},
{
internalType: 'address',
name: 'spender',
type: 'address',
},
{
internalType: 'uint256',
name: 'nonce',
type: 'uint256',
},
{
internalType: 'uint256',
name: 'expiry',
type: 'uint256',
},
{
internalType: 'bool',
name: 'allowed',
type: 'bool',
},
{
internalType: 'uint8',
name: 'v',
type: 'uint8',
},
{
internalType: 'bytes32',
name: 'r',
type: 'bytes32',
},
{
internalType: 'bytes32',
name: 's',
type: 'bytes32',
},
],
name: 'permit',
outputs: [],
payable: false,
stateMutability: 'nonpayable',
type: 'function',
},
];
*/
68 changes: 68 additions & 0 deletions src/methods/common/orders/signature.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
type SplitSignatureResult = {
v: number;
r: string;
s: string;
compact: string;
};

export function splitSignature(signature: string): SplitSignatureResult {
// Remove "0x" prefix if present
if (signature.startsWith('0x')) {
signature = signature.slice(2);
}

// Convert the hex string to a byte array
const bytes = new Uint8Array(signature.length / 2);
for (let i = 0; i < signature.length; i += 2) {
bytes[i / 2] = parseInt(signature.slice(i, i + 2), 16);
}

// Validate the signature length (64 or 65 bytes)
if (bytes.length !== 64 && bytes.length !== 65) {
throw new Error('Invalid signature length: must be 64 or 65 bytes');
}

// Extract r and s components
const r = `0x${Array.from(bytes.slice(0, 32), (b) =>
b.toString(16).padStart(2, '0')
).join('')}`;
let s: string;
let v;

// Handle 64-byte (EIP-2098 compact) and 65-byte signatures
if (bytes.length === 64) {
// Extract v from the highest bit of s and clear the bit in s
v = 27 + (bytes[32]! >> 7);
bytes[32]! &= 0x7f; // Clear the highest bit
s = `0x${Array.from(bytes.slice(32, 64))
.map((b) => b.toString(16).padStart(2, '0'))
.join('')}`;
} else {
s = `0x${Array.from(bytes.slice(32, 64))
.map((b) => b.toString(16).padStart(2, '0'))
.join('')}`;

// Extract v directly for 65-byte signature
v = bytes[64]!;

// Normalize v to canonical form (27 or 28)
if (v < 27) {
v += 27;
}
}

// Compute yParityAndS (_vs) for the compact signature
const sBytes = Array.from(bytes.slice(32, 64));
if (v === 28) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
sBytes[0]! |= 0x80; // Set the highest bit if v is 28
}
const yParityAndS = `0x${sBytes
.map((b) => b.toString(16).padStart(2, '0'))
.join('')}`;

// Construct the compact signature by concatenating r and yParityAndS
const compactSignature = r + yParityAndS.slice(2);

return { v, r, s, compact: compactSignature };
}
3 changes: 3 additions & 0 deletions src/methods/limitOrders/approveForOrder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { constructGetSpender } from '../swap/spender';
export type ApproveTokenForLimitOrderFunctions<T> = {
/** @description approving AugustusRFQ as spender for makerAsset */
approveMakerTokenForLimitOrder: ApproveToken<T>;
/** @description approving AugustusRFQ as spender for takerAsset to call SDK.fillOrderDirectly */
approveTakerTokenForFillingP2POrderDirectly: ApproveToken<T>;
/** @description approving AugustusSwapper as spender for takerAsset for Limit Orders that will be executed through it */
approveTakerTokenForLimitOrder: ApproveToken<T>;
};
Expand All @@ -28,6 +30,7 @@ export const constructApproveTokenForLimitOrder = <T>(

return {
approveMakerTokenForLimitOrder,
approveTakerTokenForFillingP2POrderDirectly: approveMakerTokenForLimitOrder,
approveTakerTokenForLimitOrder,
};
};
Loading