-
Notifications
You must be signed in to change notification settings - Fork 106
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feat/fill OTC Order with Permit (#186)
* constructFillOrderDirectly/allow for takerPermit * constructApproveTokenForLimitOrder/approveTakerTokenForFillingP2POrderDirectly * test helpers/extends buyErc20TokenForEth * tests/fix comments * tests/LO/fill OTC directly * tests/LO/more helpers * tests/LO/fill OTC directly with Permit * helpers/splitSignature * helper/encodeEIP_2612PermitFunctionInput * fillOrderDirectly/handle EIP2612 takerPermit * test/LO/OTC with Permit * helpers/encodeDAIlikePermitFunctionInput * lOrder/fillOrderDirectly/handle DAI Permit and pre-encoded permit params * test/LOrder/fill OTC with DAI Permit * cleanup * test/LO/reenable tests * update snapshots
- Loading branch information
Showing
8 changed files
with
1,289 additions
and
51 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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', | ||
}, | ||
]; | ||
*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.