Skip to content

Commit c2f6c30

Browse files
committed
Add delegation encoding and decoding.
- use these methods in @metamask/delegation-toolkit CaveatBuilders.
1 parent df5fe10 commit c2f6c30

5 files changed

Lines changed: 777 additions & 21 deletions

File tree

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import { encodeSingle, decodeSingle } from '@metamask/abi-utils';
2+
import { type BytesLike } from '@metamask/utils';
3+
4+
import {
5+
bytesLikeToBytes,
6+
bytesLikeToHex,
7+
defaultOptions,
8+
prepareResult,
9+
type EncodingOptions,
10+
type ResultValue,
11+
} from './returns';
12+
import type { Delegation, Hex } from './types';
13+
14+
/**
15+
* To be used on a delegation as the root authority.
16+
*/
17+
export const ROOT_AUTHORITY =
18+
'0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff';
19+
20+
/**
21+
* The ABI types for an array of delegations.
22+
*/
23+
const DELEGATION_ARRAY_ABI_TYPES =
24+
'(address,address,bytes32,(address,bytes,bytes)[],uint256,bytes)[]' as const;
25+
26+
/**
27+
* Encodes an array of delegations into a permission context.
28+
* @param delegations - The delegations to encode.
29+
* @param options - Encoding options. Defaults to { out: 'hex' }.
30+
* @returns The encoded delegations as a hex string (default) or Uint8Array.
31+
*/
32+
export function encodeDelegations(
33+
delegations: Delegation[],
34+
options?: EncodingOptions<'hex'>,
35+
): Hex;
36+
export function encodeDelegations(
37+
delegations: Delegation[],
38+
options: EncodingOptions<'bytes'>,
39+
): Uint8Array;
40+
/**
41+
* Encodes an array of delegations into a permission context.
42+
* @param delegations - The delegations to encode.
43+
* @param options - Encoding options. Defaults to { out: 'hex' }.
44+
* @returns The encoded delegations as a hex string (default) or Uint8Array.
45+
*/
46+
export function encodeDelegations(
47+
delegations: Delegation[],
48+
options: EncodingOptions<ResultValue> = defaultOptions,
49+
): Hex | Uint8Array {
50+
let result: Uint8Array;
51+
52+
if (delegations.length === 0) {
53+
// short circuit for empty delegations, derived from
54+
// `encode(['(address,address,bytes32,(address,bytes,bytes)[],uint256,bytes)[]'],[[]],);`
55+
result = new Uint8Array(64);
56+
result[31] = 0x20;
57+
} else {
58+
const encodableStructs = delegations.map((struct) => [
59+
struct.delegate,
60+
struct.delegator,
61+
struct.authority,
62+
struct.caveats.map((caveat) => [
63+
caveat.enforcer,
64+
caveat.terms,
65+
caveat.args,
66+
]),
67+
struct.salt,
68+
struct.signature,
69+
]);
70+
71+
result = encodeSingle(DELEGATION_ARRAY_ABI_TYPES, encodableStructs);
72+
}
73+
74+
return prepareResult(result, options);
75+
}
76+
77+
/**
78+
* Converts a decoded delegation struct to a delegation object using the provided conversion function.
79+
* @param decodedDelegation - The decoded delegation struct as a tuple.
80+
* @param convertFn - Function to convert BytesLike values to the desired output type.
81+
* @returns A delegation object with all bytes-like values converted using the provided function.
82+
*/
83+
const delegationFromDecodedDelegation = <TEncoding extends BytesLike>(
84+
decodedDelegation: DecodedDelegation,
85+
convertFn: (value: BytesLike) => TEncoding,
86+
): Delegation<TEncoding> => {
87+
const [delegate, delegator, authority, caveats, salt, signature] =
88+
decodedDelegation;
89+
90+
return {
91+
delegate: convertFn(delegate),
92+
delegator: convertFn(delegator),
93+
authority: convertFn(authority),
94+
caveats: caveats.map(([enforcer, terms, args]) => ({
95+
enforcer: convertFn(enforcer),
96+
terms: convertFn(terms),
97+
args: convertFn(args),
98+
})),
99+
salt,
100+
signature: convertFn(signature),
101+
};
102+
};
103+
104+
/**
105+
* Represents a decoded delegation as a tuple structure.
106+
* This type defines the structure of a delegation after it has been decoded from
107+
* an encoded format.
108+
*/
109+
type DecodedDelegation = [
110+
BytesLike,
111+
BytesLike,
112+
BytesLike,
113+
[BytesLike, BytesLike, BytesLike][],
114+
bigint,
115+
BytesLike,
116+
];
117+
118+
/**
119+
* Decodes an encoded permission context back into an array of delegations.
120+
* @param encoded - The encoded delegations as a hex string or Uint8Array.
121+
* @param options - Encoding options. Defaults to { out: 'hex' }.
122+
* @returns The decoded delegations array with types resolved based on options.
123+
*/
124+
export function decodeDelegations(
125+
encoded: BytesLike,
126+
options?: EncodingOptions<'hex'>,
127+
): Delegation<Hex>[];
128+
export function decodeDelegations(
129+
encoded: BytesLike,
130+
options: EncodingOptions<'bytes'>,
131+
): Delegation<Uint8Array>[];
132+
/**
133+
* Decodes an encoded permission context back into an array of delegations.
134+
* @param encoded - The encoded delegations as a hex string or Uint8Array.
135+
* @param options - Encoding options. Defaults to { out: 'hex' }.
136+
* @returns The decoded delegations array with types resolved based on options.
137+
*/
138+
export function decodeDelegations(
139+
encoded: BytesLike,
140+
options: EncodingOptions<ResultValue> = defaultOptions,
141+
): Delegation<Hex>[] | Delegation<Uint8Array>[] {
142+
// it's possible to short circuit for empty delegations, but due to the
143+
// complexity of the input type, and the relative infrequency of empty delegations,
144+
// it's not worthwhile.
145+
146+
const decodedStructs = decodeSingle(
147+
DELEGATION_ARRAY_ABI_TYPES,
148+
encoded,
149+
// return types cannot be inferred from complex ABI types, so we must assert the type
150+
) as DecodedDelegation[];
151+
152+
if (options.out === 'bytes') {
153+
return decodedStructs.map((struct) =>
154+
delegationFromDecodedDelegation(struct, bytesLikeToBytes),
155+
);
156+
}
157+
return decodedStructs.map((struct) =>
158+
delegationFromDecodedDelegation(struct, bytesLikeToHex),
159+
);
160+
}
Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export type { Hex } from './types';
1+
export type { Hex, Delegation, Caveat } from './types';
22

33
export {
44
createValueLteTerms,
@@ -7,3 +7,9 @@ export {
77
createExactCalldataTerms,
88
createNativeTokenStreamingTerms,
99
} from './caveats';
10+
11+
export {
12+
encodeDelegations,
13+
decodeDelegations,
14+
ROOT_AUTHORITY,
15+
} from './delegation';
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,35 @@
1+
import type { BytesLike } from '@metamask/utils';
2+
13
export type { Hex } from '@metamask/utils';
4+
5+
/**
6+
* Represents a caveat that restricts or conditions a delegation.
7+
*
8+
* @property enforcer - The address of the contract that enforces this caveat's conditions.
9+
* @property terms - The terms or conditions of the caveat encoded as hex data.
10+
* @property args - Additional arguments required by the caveat enforcer, encoded as hex data.
11+
*/
12+
export type Caveat<TBytes extends BytesLike = BytesLike> = {
13+
enforcer: TBytes;
14+
terms: TBytes;
15+
args: TBytes;
16+
};
17+
18+
/**
19+
* Represents a delegation that grants permissions from a delegator to a delegate.
20+
*
21+
* @property delegate - The address of the entity receiving the delegation.
22+
* @property delegator - The address of the entity granting the delegation.
23+
* @property authority - The authority under which this delegation is granted. For root delegations, this is ROOT_AUTHORITY.
24+
* @property caveats - An array of restrictions or conditions applied to this delegation.
25+
* @property salt - A unique value to prevent replay attacks and ensure uniqueness of the delegation.
26+
* @property signature - The cryptographic signature validating this delegation.
27+
*/
28+
export type Delegation<TBytes extends BytesLike = BytesLike> = {
29+
delegate: TBytes;
30+
delegator: TBytes;
31+
authority: TBytes;
32+
caveats: Caveat<TBytes>[];
33+
salt: bigint;
34+
signature: TBytes;
35+
};

0 commit comments

Comments
 (0)