Skip to content
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
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion bindings/TNT_CORE_VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
691c7c7be8421cf58c5e4f681a3e2260fa0ee1fa
7cdda757d58cc659ab8f8a37b71f6d9bb1b0f623
2 changes: 1 addition & 1 deletion bindings/abi/IBlueprintServiceManager.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion bindings/abi/IMultiAssetDelegation.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion bindings/abi/ITangle.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion bindings/abi/ITangleFull.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion bindings/abi/MultiAssetDelegation.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion bindings/abi/OperatorStatusRegistry.json

Large diffs are not rendered by default.

879 changes: 872 additions & 7 deletions bindings/src/bindings/i_tangle.rs

Large diffs are not rendered by default.

771 changes: 704 additions & 67 deletions bindings/src/bindings/i_tangle_blueprints.rs

Large diffs are not rendered by default.

921 changes: 893 additions & 28 deletions bindings/src/bindings/i_tangle_full.rs

Large diffs are not rendered by default.

230 changes: 229 additions & 1 deletion bindings/src/bindings/i_tangle_services.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5850,6 +5850,7 @@ interface ITangleServices {
function getServiceEscrow(uint64 serviceId) external view returns (PaymentLib.ServiceEscrow memory);
function getServiceOperator(uint64 serviceId, address operator) external view returns (Types.ServiceOperator memory);
function getServiceOperators(uint64 serviceId) external view returns (address[] memory);
function getServicePaymentAsset(uint64 serviceId) external view returns (address);
function getServiceRequest(uint64 requestId) external view returns (Types.ServiceRequest memory);
function getServiceRequestResourceRequirements(uint64 requestId) external view returns (Types.ResourceCommitment[] memory);
function getServiceRequestSecurityCommitments(uint64 requestId, address operator) external view returns (Types.AssetSecurityCommitment[] memory);
Expand Down Expand Up @@ -6773,6 +6774,25 @@ interface ITangleServices {
],
"stateMutability": "view"
},
{
"type": "function",
"name": "getServicePaymentAsset",
"inputs": [
{
"name": "serviceId",
"type": "uint64",
"internalType": "uint64"
}
],
"outputs": [
{
"name": "",
"type": "address",
"internalType": "address"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "getServiceRequest",
Expand Down Expand Up @@ -12771,6 +12791,162 @@ function getServiceOperators(uint64 serviceId) external view returns (address[]
};
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Default, Debug, PartialEq, Eq, Hash)]
/**Function with signature `getServicePaymentAsset(uint64)` and selector `0xfc99b3d3`.
```solidity
function getServicePaymentAsset(uint64 serviceId) external view returns (address);
```*/
#[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)]
#[derive(Clone)]
pub struct getServicePaymentAssetCall {
#[allow(missing_docs)]
pub serviceId: u64,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Default, Debug, PartialEq, Eq, Hash)]
///Container type for the return parameters of the [`getServicePaymentAsset(uint64)`](getServicePaymentAssetCall) function.
#[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)]
#[derive(Clone)]
pub struct getServicePaymentAssetReturn {
#[allow(missing_docs)]
pub _0: alloy::sol_types::private::Address,
}
#[allow(
non_camel_case_types,
non_snake_case,
clippy::pub_underscore_fields,
clippy::style
)]
const _: () = {
use alloy::sol_types as alloy_sol_types;
{
#[doc(hidden)]
#[allow(dead_code)]
type UnderlyingSolTuple<'a> = (alloy::sol_types::sol_data::Uint<64>,);
#[doc(hidden)]
type UnderlyingRustTuple<'a> = (u64,);
#[cfg(test)]
#[allow(dead_code, unreachable_patterns)]
fn _type_assertion(
_t: alloy_sol_types::private::AssertTypeEq<UnderlyingRustTuple>,
) {
match _t {
alloy_sol_types::private::AssertTypeEq::<
<UnderlyingSolTuple as alloy_sol_types::SolType>::RustType,
>(_) => {}
}
}
#[automatically_derived]
#[doc(hidden)]
impl ::core::convert::From<getServicePaymentAssetCall>
for UnderlyingRustTuple<'_> {
fn from(value: getServicePaymentAssetCall) -> Self {
(value.serviceId,)
}
}
#[automatically_derived]
#[doc(hidden)]
impl ::core::convert::From<UnderlyingRustTuple<'_>>
for getServicePaymentAssetCall {
fn from(tuple: UnderlyingRustTuple<'_>) -> Self {
Self { serviceId: tuple.0 }
}
}
}
{
#[doc(hidden)]
#[allow(dead_code)]
type UnderlyingSolTuple<'a> = (alloy::sol_types::sol_data::Address,);
#[doc(hidden)]
type UnderlyingRustTuple<'a> = (alloy::sol_types::private::Address,);
#[cfg(test)]
#[allow(dead_code, unreachable_patterns)]
fn _type_assertion(
_t: alloy_sol_types::private::AssertTypeEq<UnderlyingRustTuple>,
) {
match _t {
alloy_sol_types::private::AssertTypeEq::<
<UnderlyingSolTuple as alloy_sol_types::SolType>::RustType,
>(_) => {}
}
}
#[automatically_derived]
#[doc(hidden)]
impl ::core::convert::From<getServicePaymentAssetReturn>
for UnderlyingRustTuple<'_> {
fn from(value: getServicePaymentAssetReturn) -> Self {
(value._0,)
}
}
#[automatically_derived]
#[doc(hidden)]
impl ::core::convert::From<UnderlyingRustTuple<'_>>
for getServicePaymentAssetReturn {
fn from(tuple: UnderlyingRustTuple<'_>) -> Self {
Self { _0: tuple.0 }
}
}
}
#[automatically_derived]
impl alloy_sol_types::SolCall for getServicePaymentAssetCall {
type Parameters<'a> = (alloy::sol_types::sol_data::Uint<64>,);
type Token<'a> = <Self::Parameters<
'a,
> as alloy_sol_types::SolType>::Token<'a>;
type Return = alloy::sol_types::private::Address;
type ReturnTuple<'a> = (alloy::sol_types::sol_data::Address,);
type ReturnToken<'a> = <Self::ReturnTuple<
'a,
> as alloy_sol_types::SolType>::Token<'a>;
const SIGNATURE: &'static str = "getServicePaymentAsset(uint64)";
const SELECTOR: [u8; 4] = [252u8, 153u8, 179u8, 211u8];
#[inline]
fn new<'a>(
tuple: <Self::Parameters<'a> as alloy_sol_types::SolType>::RustType,
) -> Self {
tuple.into()
}
#[inline]
fn tokenize(&self) -> Self::Token<'_> {
(
<alloy::sol_types::sol_data::Uint<
64,
> as alloy_sol_types::SolType>::tokenize(&self.serviceId),
)
}
#[inline]
fn tokenize_returns(ret: &Self::Return) -> Self::ReturnToken<'_> {
(
<alloy::sol_types::sol_data::Address as alloy_sol_types::SolType>::tokenize(
ret,
),
)
}
#[inline]
fn abi_decode_returns(data: &[u8]) -> alloy_sol_types::Result<Self::Return> {
<Self::ReturnTuple<
'_,
> as alloy_sol_types::SolType>::abi_decode_sequence(data)
.map(|r| {
let r: getServicePaymentAssetReturn = r.into();
r._0
})
}
#[inline]
fn abi_decode_returns_validate(
data: &[u8],
) -> alloy_sol_types::Result<Self::Return> {
<Self::ReturnTuple<
'_,
> as alloy_sol_types::SolType>::abi_decode_sequence_validate(data)
.map(|r| {
let r: getServicePaymentAssetReturn = r.into();
r._0
})
}
}
};
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Default, Debug, PartialEq, Eq, Hash)]
/**Function with signature `getServiceRequest(uint64)` and selector `0x5f9b4dfa`.
```solidity
function getServiceRequest(uint64 requestId) external view returns (Types.ServiceRequest memory);
Expand Down Expand Up @@ -17260,6 +17436,8 @@ function withdrawRemainingEscrowTo(uint64 serviceId, address to) external;
#[allow(missing_docs)]
getServiceOperators(getServiceOperatorsCall),
#[allow(missing_docs)]
getServicePaymentAsset(getServicePaymentAssetCall),
#[allow(missing_docs)]
getServiceRequest(getServiceRequestCall),
#[allow(missing_docs)]
getServiceRequestResourceRequirements(getServiceRequestResourceRequirementsCall),
Expand Down Expand Up @@ -17368,6 +17546,7 @@ function withdrawRemainingEscrowTo(uint64 serviceId, address to) external;
[228u8, 192u8, 183u8, 86u8],
[229u8, 247u8, 151u8, 242u8],
[235u8, 140u8, 59u8, 205u8],
[252u8, 153u8, 179u8, 211u8],
];
/// The names of the variants in the same order as `SELECTORS`.
pub const VARIANT_NAMES: &'static [&'static str] = &[
Expand Down Expand Up @@ -17419,6 +17598,7 @@ function withdrawRemainingEscrowTo(uint64 serviceId, address to) external;
::core::stringify!(getServiceResourceCommitmentHash),
::core::stringify!(getServiceRequestSecurityRequirements),
::core::stringify!(getServiceEscrow),
::core::stringify!(getServicePaymentAsset),
];
/// The signatures in the same order as `SELECTORS`.
pub const SIGNATURES: &'static [&'static str] = &[
Expand Down Expand Up @@ -17470,6 +17650,7 @@ function withdrawRemainingEscrowTo(uint64 serviceId, address to) external;
<getServiceResourceCommitmentHashCall as alloy_sol_types::SolCall>::SIGNATURE,
<getServiceRequestSecurityRequirementsCall as alloy_sol_types::SolCall>::SIGNATURE,
<getServiceEscrowCall as alloy_sol_types::SolCall>::SIGNATURE,
<getServicePaymentAssetCall as alloy_sol_types::SolCall>::SIGNATURE,
];
/// Returns the signature for the given selector, if known.
#[inline]
Expand All @@ -17496,7 +17677,7 @@ function withdrawRemainingEscrowTo(uint64 serviceId, address to) external;
impl alloy_sol_types::SolInterface for ITangleServicesCalls {
const NAME: &'static str = "ITangleServicesCalls";
const MIN_DATA_LENGTH: usize = 0usize;
const COUNT: usize = 48usize;
const COUNT: usize = 49usize;
#[inline]
fn selector(&self) -> [u8; 4] {
match self {
Expand Down Expand Up @@ -17566,6 +17747,9 @@ function withdrawRemainingEscrowTo(uint64 serviceId, address to) external;
Self::getServiceOperators(_) => {
<getServiceOperatorsCall as alloy_sol_types::SolCall>::SELECTOR
}
Self::getServicePaymentAsset(_) => {
<getServicePaymentAssetCall as alloy_sol_types::SolCall>::SELECTOR
}
Self::getServiceRequest(_) => {
<getServiceRequestCall as alloy_sol_types::SolCall>::SELECTOR
}
Expand Down Expand Up @@ -18195,6 +18379,17 @@ function withdrawRemainingEscrowTo(uint64 serviceId, address to) external;
}
getServiceEscrow
},
{
fn getServicePaymentAsset(
data: &[u8],
) -> alloy_sol_types::Result<ITangleServicesCalls> {
<getServicePaymentAssetCall as alloy_sol_types::SolCall>::abi_decode_raw(
data,
)
.map(ITangleServicesCalls::getServicePaymentAsset)
}
getServicePaymentAsset
},
];
let Ok(idx) = Self::SELECTORS.binary_search(&selector) else {
return Err(
Expand Down Expand Up @@ -18749,6 +18944,17 @@ function withdrawRemainingEscrowTo(uint64 serviceId, address to) external;
}
getServiceEscrow
},
{
fn getServicePaymentAsset(
data: &[u8],
) -> alloy_sol_types::Result<ITangleServicesCalls> {
<getServicePaymentAssetCall as alloy_sol_types::SolCall>::abi_decode_raw_validate(
data,
)
.map(ITangleServicesCalls::getServicePaymentAsset)
}
getServicePaymentAsset
},
];
let Ok(idx) = Self::SELECTORS.binary_search(&selector) else {
return Err(
Expand Down Expand Up @@ -18867,6 +19073,11 @@ function withdrawRemainingEscrowTo(uint64 serviceId, address to) external;
inner,
)
}
Self::getServicePaymentAsset(inner) => {
<getServicePaymentAssetCall as alloy_sol_types::SolCall>::abi_encoded_size(
inner,
)
}
Self::getServiceRequest(inner) => {
<getServiceRequestCall as alloy_sol_types::SolCall>::abi_encoded_size(
inner,
Expand Down Expand Up @@ -19134,6 +19345,12 @@ function withdrawRemainingEscrowTo(uint64 serviceId, address to) external;
out,
)
}
Self::getServicePaymentAsset(inner) => {
<getServicePaymentAssetCall as alloy_sol_types::SolCall>::abi_encode_raw(
inner,
out,
)
}
Self::getServiceRequest(inner) => {
<getServiceRequestCall as alloy_sol_types::SolCall>::abi_encode_raw(
inner,
Expand Down Expand Up @@ -20008,6 +20225,17 @@ the bytecode concatenated with the constructor's ABI-encoded arguments.*/
},
)
}
///Creates a new call builder for the [`getServicePaymentAsset`] function.
pub fn getServicePaymentAsset(
&self,
serviceId: u64,
) -> alloy_contract::SolCallBuilder<&P, getServicePaymentAssetCall, N> {
self.call_builder(
&getServicePaymentAssetCall {
serviceId,
},
)
}
///Creates a new call builder for the [`getServiceRequest`] function.
pub fn getServiceRequest(
&self,
Expand Down
44 changes: 39 additions & 5 deletions src/TangleStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -541,14 +541,48 @@ abstract contract TangleStorage {
/// hook clears; other chains keep the tight 500k DoS bound. Bounded either way.
uint256 internal _managerHookGasLimit;

// ═══════════════════════════════════════════════════════════════════════════
// EVENT-DRIVEN SETTLEMENT ASSET (appended 2026-07-08)
// ═══════════════════════════════════════════════════════════════════════════
// Appended after `_managerHookGasLimit` (never inserted mid layout); the reserved
// gap below is shrunk by two to preserve total storage size for upgrade safety.

/// @notice Service ID => EventDriven settlement asset. `address(0)` = native.
/// @dev Pinned once at activation from the SERVICE's BLUEPRINT settlement asset
/// (`_blueprintSettlementAsset[blueprintId]`, set by the blueprint owner), NOT
/// from the customer's request — see `TangleServicesFacet._handleInitialPayments`.
/// Read by the per-job collection/distribution paths
/// (`JobsSubmission._collectJobPaymentIfNeeded`, the Jobs/RFQ distribute
/// overrides) so per-job billing settles in the currency the blueprint DEVELOPER
/// declared — native OR a manager-allowed ERC20 (e.g. Tempo PathUSD) — instead of
/// a native-only hardcode. Pinning per-service is deliberate: a later change to the
/// blueprint's asset must NOT re-price services that are already live. Only written
/// for EventDriven services; other pricing models leave it at the `address(0)`
/// default, which is also the correct native sentinel, so a stale read can never
/// misroute a non-EventDriven payment.
mapping(uint64 => address) internal _serviceEventDrivenAsset;

/// @notice Blueprint ID => EventDriven settlement asset the developer declares for the
/// blueprint's services. `address(0)` = native (the default and the sentinel).
/// @dev Set by the blueprint owner via `setBlueprintSettlementAsset`; the SAME party that
/// sets the per-job rate (`setJobEventRates`) chooses the settlement asset, so the
/// rate's units always match the asset's decimals. Read once at activation to pin
/// `_serviceEventDrivenAsset[serviceId]`. The customer CANNOT choose the settlement
/// asset at request time (an EventDriven request must pass native `address(0)`); this
/// is what makes the 10^12x under-payment exploit — a 6-dec rate driven to settle in
/// native — structurally impossible: there is no path to a service whose settlement
/// asset differs from its blueprint's declared asset.
mapping(uint64 => address) internal _blueprintSettlementAsset;

// ═══════════════════════════════════════════════════════════════════════════
// RESERVED STORAGE GAP
// ═══════════════════════════════════════════════════════════════════════════

/// @dev Reserved storage slots for future appends. __gap size 27, occupying slots
/// 91..117 (the sequential layout ends at _managerHookGasLimit in slot 90
/// @dev Reserved storage slots for future appends. __gap size 25, occupying slots
/// 92..116 (the sequential layout ends at _blueprintSettlementAsset in slot 91
/// verify with `forge inspect Tangle storage-layout`; the tail slots are pinned
/// by StorageLayoutSnapshotTest). Appending a field consumes gap slots
/// one-for-one: shrink this array by the field's slot count in the same change.
uint256[27] private __gap;
/// by StorageLayoutSnapshotTest, which fixes _managerHookGasLimit at slot 89, so the
/// two appended asset mappings sit at slots 90 and 91). Appending a field consumes gap
/// slots one-for-one: shrink this array by the field's slot count in the same change.
uint256[25] private __gap;
}
Loading
Loading