Skip to content

Commit 72467d7

Browse files
decofegrandizzy
andauthored
feat: add T3 hardfork support (#91)
* feat: add T3 hardfork support - IAccountKeychain: T3 ABI with call scopes (CallScope, SelectorRule), periodic token limits (TokenLimit.period), KeyRestrictions struct, authorizeKey V2, setAllowedCalls, removeAllowedCalls, getAllowedCalls, getRemainingLimitWithPeriod, new errors and AccessKeySpend event - StdPrecompiles: add ADDRESS_REGISTRY, SIGNATURE_VERIFIER, and VALIDATOR_CONFIG_V2 addresses and typed constants Co-Authored-By: grandizzy <38490174+grandizzy@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d5352-e28f-72dd-b87b-befc7fce5cf8 * chore: forge fmt Co-Authored-By: grandizzy <38490174+grandizzy@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d5352-e28f-72dd-b87b-befc7fce5cf8 --------- Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com>
1 parent fe87b9e commit 72467d7

File tree

3 files changed

+120
-16
lines changed

3 files changed

+120
-16
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@ forge install tempoxyz/tempo-std
2828
<pre>
2929
src
3030
├── interfaces
31-
│ ├── <a href="./src/interfaces/IAccountKeychain.sol">IAccountKeychain.sol</a>: Account Keychain | <a href="https://docs.tempo.xyz/protocol/transactions/spec-tempo-transaction#keychain-precompile">Docs</a> | <a href="https://github.com/tempoxyz/tempo/blob/main/crates/precompiles/src/account_keychain/mod.rs">Implementation</a>
32-
│ ├── <a href="./src/interfaces/IAddressRegistry.sol">IAddressRegistry.sol</a>: TIP-1022 Virtual Address Registry | <a href="https://github.com/tempoxyz/tempo/blob/main/crates/precompiles/src/address_registry/mod.rs">Implementation</a>
31+
│ ├── <a href="./src/interfaces/IAccountKeychain.sol">IAccountKeychain.sol</a>: Account Keychain (T3: call scopes, periodic limits) | <a href="https://docs.tempo.xyz/protocol/transactions/spec-tempo-transaction#keychain-precompile">Docs</a> | <a href="https://github.com/tempoxyz/tempo/blob/main/crates/precompiles/src/account_keychain/mod.rs">Implementation</a>
32+
│ ├── <a href="./src/interfaces/IAddressRegistry.sol">IAddressRegistry.sol</a>: TIP-1022 Virtual Address Registry (T3+) | <a href="https://github.com/tempoxyz/tempo/blob/main/crates/precompiles/src/address_registry/mod.rs">Implementation</a>
3333
│ ├── <a href="./src/interfaces/IFeeAMM.sol">IFeeAMM.sol</a>: Fee AMM | <a href="https://docs.tempo.xyz/protocol/fees/fee-amm">Docs</a> | <a href="https://github.com/tempoxyz/tempo/blob/main/crates/precompiles/src/tip_fee_manager/amm.rs">Implementation</a>
3434
│ ├── <a href="./src/interfaces/IFeeManager.sol">IFeeManager.sol</a>: Fee AMM Management | <a href="https://docs.tempo.xyz/protocol/fees/spec-fee-amm#2-feemanager-contract">Docs</a> | <a href="https://github.com/tempoxyz/tempo/blob/main/crates/precompiles/src/tip_fee_manager/mod.rs">Implementation</a>
3535
│ ├── <a href="./src/interfaces/INonce.sol">INonce.sol</a>: 2D Nonce Management for Tempo Transactions | <a href="https://github.com/tempoxyz/tempo/blob/main/crates/precompiles/src/nonce/mod.rs">Implementation</a>
36-
│ ├── <a href="./src/interfaces/ISignatureVerifier.sol">ISignatureVerifier.sol</a>: TIP-1020 Signature Verification Precompile | <a href="https://github.com/tempoxyz/tempo/blob/main/crates/precompiles/src/signature_verifier/mod.rs">Implementation</a>
36+
│ ├── <a href="./src/interfaces/ISignatureVerifier.sol">ISignatureVerifier.sol</a>: TIP-1020 Signature Verification Precompile (T3+) | <a href="https://github.com/tempoxyz/tempo/blob/main/crates/precompiles/src/signature_verifier/mod.rs">Implementation</a>
3737
│ ├── <a href="./src/interfaces/IStablecoinDEX.sol">IStablecoinDEX.sol</a>: Stablecoin DEX | <a href="https://docs.tempo.xyz/protocol/exchange/spec#stablecoin-dex">Docs</a> | <a href="https://github.com/tempoxyz/tempo/blob/main/crates/precompiles/src/stablecoin_dex/mod.rs">Implementation</a>
3838
│ ├── <a href="./src/interfaces/ITempoStreamChannel.sol">ITempoStreamChannel.sol</a>: Streaming payment channel escrow (concept) | <a href="https://github.com/tempoxyz/tempo/blob/main/tips/ref-impls/src/TempoStreamChannel.sol">Implementation</a>
3939
│ ├── <a href="./src/interfaces/ITIP20Factory.sol">ITIP20Factory.sol</a>: TIP-20: Factory Contract | <a href="https://docs.tempo.xyz/protocol/tip20/spec#tip20factory">Docs</a> | <a href="https://github.com/tempoxyz/tempo/blob/main/crates/precompiles/src/tip20_factory/mod.rs">Implementation</a>

src/StdPrecompiles.sol

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22
pragma solidity >=0.8.13 <0.9.0;
33

44
import {IAccountKeychain} from "./interfaces/IAccountKeychain.sol";
5+
import {IAddressRegistry} from "./interfaces/IAddressRegistry.sol";
56
import {IFeeManager} from "./interfaces/IFeeManager.sol";
7+
import {ISignatureVerifier} from "./interfaces/ISignatureVerifier.sol";
68
import {ITIP403Registry} from "./interfaces/ITIP403Registry.sol";
79
import {ITIP20Factory} from "./interfaces/ITIP20Factory.sol";
810
import {ITIP20RewardsRegistry} from "./interfaces/ITIP20RewardsRegistry.sol";
911
import {IStablecoinDEX} from "./interfaces/IStablecoinDEX.sol";
1012
import {IValidatorConfig} from "./interfaces/IValidatorConfig.sol";
13+
import {IValidatorConfigV2} from "./interfaces/IValidatorConfigV2.sol";
1114
import {INonce} from "./interfaces/INonce.sol";
1215

1316
/// @title Standard Precompiles Library for Tempo
@@ -22,6 +25,9 @@ library StdPrecompiles {
2225
address internal constant NONCE_ADDRESS = 0x4e4F4E4345000000000000000000000000000000;
2326
address internal constant VALIDATOR_CONFIG_ADDRESS = 0xCccCcCCC00000000000000000000000000000000;
2427
address internal constant ACCOUNT_KEYCHAIN_ADDRESS = 0xaAAAaaAA00000000000000000000000000000000;
28+
address internal constant VALIDATOR_CONFIG_V2_ADDRESS = 0xCcCCCCcC00000000000000000000000000000001;
29+
address internal constant ADDRESS_REGISTRY_ADDRESS = 0xfDC0000000000000000000000000000000000000;
30+
address internal constant SIGNATURE_VERIFIER_ADDRESS = 0x5165300000000000000000000000000000000000;
2531

2632
IFeeManager internal constant TIP_FEE_MANAGER = IFeeManager(TIP_FEE_MANAGER_ADDRESS);
2733
ITIP403Registry internal constant TIP403_REGISTRY = ITIP403Registry(TIP403_REGISTRY_ADDRESS);
@@ -32,4 +38,7 @@ library StdPrecompiles {
3238
INonce internal constant NONCE_PRECOMPILE = INonce(NONCE_ADDRESS);
3339
IValidatorConfig internal constant VALIDATOR_CONFIG = IValidatorConfig(VALIDATOR_CONFIG_ADDRESS);
3440
IAccountKeychain internal constant ACCOUNT_KEYCHAIN = IAccountKeychain(ACCOUNT_KEYCHAIN_ADDRESS);
41+
IValidatorConfigV2 internal constant VALIDATOR_CONFIG_V2 = IValidatorConfigV2(VALIDATOR_CONFIG_V2_ADDRESS);
42+
IAddressRegistry internal constant ADDRESS_REGISTRY = IAddressRegistry(ADDRESS_REGISTRY_ADDRESS);
43+
ISignatureVerifier internal constant SIGNATURE_VERIFIER = ISignatureVerifier(SIGNATURE_VERIFIER_ADDRESS);
3544
}

src/interfaces/IAccountKeychain.sol

Lines changed: 108 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@ pragma solidity >=0.8.13 <0.9.0;
99
* The Account Keychain allows accounts to authorize secondary keys (Access Keys) that can sign
1010
* transactions on behalf of the account. Access Keys can be scoped by:
1111
* - Expiry timestamp (when the key becomes invalid)
12-
* - Per-TIP20 token spending limits that deplete as the key spends
12+
* - Per-TIP20 token spending limits (one-time or periodic) that deplete as the key spends
13+
* - Call scopes restricting which contracts/selectors the key may call (T3+)
1314
*
14-
* Only the Root Key can call authorizeKey, revokeKey, and updateSpendingLimit.
15+
* Only the Root Key can call authorizeKey, revokeKey, updateSpendingLimit, setAllowedCalls,
16+
* and removeAllowedCalls.
1517
* This restriction is enforced by the protocol at transaction validation time.
1618
* Access Keys attempting to call these functions will fail with UnauthorizedCaller.
1719
*
@@ -30,10 +32,38 @@ interface IAccountKeychain {
3032
WebAuthn
3133
}
3234

35+
/// @notice Legacy token spending limit structure used before T3
36+
struct LegacyTokenLimit {
37+
address token; // TIP20 token address
38+
uint256 amount; // Spending limit amount
39+
}
40+
3341
/// @notice Token spending limit structure
3442
struct TokenLimit {
3543
address token; // TIP20 token address
3644
uint256 amount; // Spending limit amount
45+
uint64 period; // Period duration in seconds (0 = one-time limit, >0 = periodic reset)
46+
}
47+
48+
/// @notice Selector-level recipient rule
49+
struct SelectorRule {
50+
bytes4 selector; // 4-byte function selector
51+
address[] recipients; // Empty means no recipient restriction for this selector
52+
}
53+
54+
/// @notice Per-target call scope
55+
struct CallScope {
56+
address target; // Target contract address
57+
SelectorRule[] selectorRules; // Empty means any selector is allowed for this target
58+
}
59+
60+
/// @notice Optional access-key restrictions configured at authorization time
61+
struct KeyRestrictions {
62+
uint64 expiry; // Unix timestamp when key expires (use type(uint64).max for never)
63+
bool enforceLimits; // Whether spending limits are enforced for this key
64+
TokenLimit[] limits; // Token spending limits
65+
bool allowAnyCalls; // true = unrestricted calls (allowedCalls must be empty), false = allowedCalls defines scope
66+
CallScope[] allowedCalls; // Call scopes when allowAnyCalls is false
3767
}
3868

3969
/// @notice Key information structure
@@ -60,29 +90,41 @@ interface IAccountKeychain {
6090
address indexed account, address indexed publicKey, address indexed token, uint256 newLimit
6191
);
6292

93+
/// @notice Emitted when an access key spends tokens
94+
event AccessKeySpend(
95+
address indexed account,
96+
address indexed publicKey,
97+
address indexed token,
98+
uint256 amount,
99+
uint256 remainingLimit
100+
);
101+
63102
/*//////////////////////////////////////////////////////////////
64103
ERRORS
65104
//////////////////////////////////////////////////////////////*/
66105

106+
error UnauthorizedCaller();
67107
error KeyAlreadyExists();
68108
error KeyNotFound();
69-
error KeyInactive();
70109
error KeyExpired();
71-
error KeyAlreadyRevoked();
72110
error SpendingLimitExceeded();
111+
error InvalidSpendingLimit();
73112
error InvalidSignatureType();
74113
error ZeroPublicKey();
75114
error ExpiryInPast();
76-
error UnauthorizedCaller();
115+
error KeyAlreadyRevoked();
116+
error SignatureTypeMismatch(uint8 expected, uint8 actual);
117+
error CallNotAllowed();
118+
error InvalidCallScope();
119+
error LegacyAuthorizeKeySelectorChanged(bytes4 newSelector);
77120

78121
/*//////////////////////////////////////////////////////////////
79122
MANAGEMENT FUNCTIONS
80123
//////////////////////////////////////////////////////////////*/
81124

82125
/**
83-
* @notice Authorize a new key for the caller's account
126+
* @notice Legacy authorize-key entrypoint used before T3
84127
* @dev MUST only be called in transactions signed by the Root Key
85-
* The protocol enforces this restriction by checking transactionKey[msg.sender]
86128
* @param keyId The key identifier (address) to authorize
87129
* @param signatureType Signature type of the key (0: Secp256k1, 1: P256, 2: WebAuthn)
88130
* @param expiry Unix timestamp when key expires (use type(uint64).max for never expires)
@@ -94,27 +136,52 @@ interface IAccountKeychain {
94136
SignatureType signatureType,
95137
uint64 expiry,
96138
bool enforceLimits,
97-
TokenLimit[] calldata limits
139+
LegacyTokenLimit[] calldata limits
98140
) external;
99141

142+
/**
143+
* @notice Authorize a new key for the caller's account with T3 extensions
144+
* @dev MUST only be called in transactions signed by the Root Key
145+
* @param keyId The key identifier (address derived from public key)
146+
* @param signatureType Signature type of the key (0: Secp256k1, 1: P256, 2: WebAuthn)
147+
* @param config Access-key expiry and optional limits / call restrictions
148+
*/
149+
function authorizeKey(address keyId, SignatureType signatureType, KeyRestrictions calldata config) external;
150+
100151
/**
101152
* @notice Revoke an authorized key
102153
* @dev MUST only be called in transactions signed by the Root Key
103-
* The protocol enforces this restriction by checking transactionKey[msg.sender]
104154
* @param keyId The key ID to revoke
105155
*/
106156
function revokeKey(address keyId) external;
107157

108158
/**
109159
* @notice Update spending limit for a specific token on an authorized key
110160
* @dev MUST only be called in transactions signed by the Root Key
111-
* The protocol enforces this restriction by checking transactionKey[msg.sender]
112161
* @param keyId The key ID to update
113162
* @param token The token address
114163
* @param newLimit The new spending limit
115164
*/
116165
function updateSpendingLimit(address keyId, address token, uint256 newLimit) external;
117166

167+
/**
168+
* @notice Set or replace allowed calls for one or more key+target pairs
169+
* @dev MUST only be called in transactions signed by the Root Key.
170+
* Reverts if `scopes` is empty; use `removeAllowedCalls` to delete target scopes.
171+
* `scope.selectorRules = []` allows any selector on that target.
172+
* @param keyId The key ID to configure
173+
* @param scopes The call scopes to set
174+
*/
175+
function setAllowedCalls(address keyId, CallScope[] calldata scopes) external;
176+
177+
/**
178+
* @notice Remove any configured call scope for a key+target pair
179+
* @dev MUST only be called in transactions signed by the Root Key
180+
* @param keyId The key ID to update
181+
* @param target The target contract to remove from allowed calls
182+
*/
183+
function removeAllowedCalls(address keyId, address target) external;
184+
118185
/*//////////////////////////////////////////////////////////////
119186
VIEW FUNCTIONS
120187
//////////////////////////////////////////////////////////////*/
@@ -128,13 +195,41 @@ interface IAccountKeychain {
128195
function getKey(address account, address keyId) external view returns (KeyInfo memory);
129196

130197
/**
131-
* @notice Get remaining spending limit for a key-token pair
198+
* @notice Get remaining spending limit for a key-token pair (legacy)
132199
* @param account The account address
133200
* @param keyId The key ID
134201
* @param token The token address
135-
* @return Remaining spending amount
202+
* @return remaining Remaining spending amount
203+
*/
204+
function getRemainingLimit(address account, address keyId, address token) external view returns (uint256 remaining);
205+
206+
/**
207+
* @notice Get remaining spending limit together with the active period end
208+
* @param account The account address
209+
* @param keyId The key ID
210+
* @param token The token address
211+
* @return remaining Remaining spending amount
212+
* @return periodEnd Period end timestamp for periodic limits (0 for one-time)
213+
*/
214+
function getRemainingLimitWithPeriod(address account, address keyId, address token)
215+
external
216+
view
217+
returns (uint256 remaining, uint64 periodEnd);
218+
219+
/**
220+
* @notice Returns whether an account key is call-scoped and, if so, the configured call scopes
221+
* @dev `isScoped = false` means unrestricted. `isScoped = true && scopes.length == 0`
222+
* means scoped deny-all. Missing, revoked, or expired access keys also return scoped
223+
* deny-all so callers do not observe stale persisted scope state.
224+
* @param account The account address
225+
* @param keyId The key ID
226+
* @return isScoped Whether the key is call-scoped
227+
* @return scopes The configured call scopes
136228
*/
137-
function getRemainingLimit(address account, address keyId, address token) external view returns (uint256);
229+
function getAllowedCalls(address account, address keyId)
230+
external
231+
view
232+
returns (bool isScoped, CallScope[] memory scopes);
138233

139234
/**
140235
* @notice Get the transaction key used in the current transaction

0 commit comments

Comments
 (0)