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
58 changes: 38 additions & 20 deletions contracts/facets/IexecPoco1Facet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -125,16 +125,17 @@ contract IexecPoco1Facet is
if (!_isAccountAuthorizedByRestriction(datasetOrder.requesterrestrict, deal.requester)) {
revert IncompatibleDatasetOrder("Requester restriction not satisfied");
}
// The deal's tag should include all tag bits of the dataset order.
// For dataset orders: ignore Scone, Gramine, and TDX framework bits to allow
// dataset orders from SGX workerpools to be consumed on TDX workerpools and vice versa.
// Examples after masking:
// Deal: 0b0101, Dataset: 0b0101 => Masked Dataset: 0b0001 => ok
// Deal: 0b0101, Dataset: 0b0001 => Masked Dataset: 0b0001 => ok
// Deal: 0b1001 (TDX), Dataset: 0b0011 (Scone) => Masked Dataset: 0b0001 => ok (cross-framework compatibility)
bytes32 maskedDatasetTag = datasetOrder.tag &
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1;
if ((deal.tag & maskedDatasetTag) != maskedDatasetTag) {
// The deal's tag should include all activated tag bits of the dataset order.
// For the dataset tag, bits of TEE frameworks (Scone, Gramine, and TDX) must be ignored
// to allow existing dataset orders with SGX tags to be consumed on TDX workerpools.
// Examples:
// Deal: 0b0101, Dataset: 0b0101 (final: 0b0001) => ok
// Deal: 0b0101, Dataset: 0b0001 (final: 0b0001) => ok
// Deal: 0b0000, Dataset: 0b0001 (final: 0b0001) => !ok
// Cross-framework examples:
// Deal: 0b1001 (TDX), Dataset: 0b0011 (Scone) (final: 0b0001) => ok
bytes32 finalDatasetTag = _ignoreTeeFramework(datasetOrder.tag);
if ((deal.tag & finalDatasetTag) != finalDatasetTag) {
revert IncompatibleDatasetOrder("Tag compatibility not satisfied");
}
}
Expand Down Expand Up @@ -229,8 +230,7 @@ contract IexecPoco1Facet is
/**
* Check orders compatibility
*/

// computation environment & allowed enough funds
// Check computation environment & prices.
require(_requestorder.category == _workerpoolorder.category, "iExecV5-matchOrders-0x00");
require(_requestorder.category < $.m_categories.length, "iExecV5-matchOrders-0x01");
require(_requestorder.trust <= _workerpoolorder.trust, "iExecV5-matchOrders-0x02");
Expand All @@ -243,14 +243,11 @@ contract IexecPoco1Facet is
_requestorder.workerpoolmaxprice >= _workerpoolorder.workerpoolprice,
"iExecV5-matchOrders-0x05"
);
// The workerpool tag should include all tag bits of dataset, app, and requester orders.
// For dataset orders: ignore Scone, Gramine, and TDX framework bits to allow
// dataset orders from SGX workerpools to be consumed on TDX workerpools and vice versa.
// Bit positions: bit 0 = TEE, bit 1 = Scone, bit 2 = Gramine, bit 3 = TDX
// Mask: ~(BIT_SCONE | BIT_GRAMINE | BIT_TDX) = ~0xE = 0xFFF...FF1
bytes32 maskedDatasetTag = _datasetorder.tag &
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1;
bytes32 tag = _apporder.tag | maskedDatasetTag | _requestorder.tag;
// Check tags compatibility:
// - The workerpool tag should include all activated tag bits of dataset, app, and requester orders.
// - For the dataset tag, bits of TEE frameworks (Scone, Gramine, and TDX) must be ignored
// to allow existing dataset orders with SGX tags to be consumed on TDX workerpools.
bytes32 tag = _apporder.tag | _ignoreTeeFramework(_datasetorder.tag) | _requestorder.tag;
require(tag & ~_workerpoolorder.tag == 0x0, "iExecV5-matchOrders-0x06");
require((tag ^ _apporder.tag)[31] & 0x01 == 0x0, "iExecV5-matchOrders-0x07");

Expand Down Expand Up @@ -474,4 +471,25 @@ contract IexecPoco1Facet is

return dealid;
}

/**
* Ignore TEE framework bits (Scone, Gramine, TDX) in the provided tag to allow
* cross-framework compatibility.
*
* Ignored bit positions in the tag:
* 0b(31 bytes...)1111
* ||||
* |||└─ bit 0: TEE
* ||└── bit 1: Scone (ignored)
* |└─── bit 2: Gramine (ignored)
* └──── bit 3: TDX (ignored)
*
* @param tag original tag
* @return tag with TEE framework bits ignored
*/
function _ignoreTeeFramework(bytes32 tag) private pure returns (bytes32) {
// (BIT_SCONE | BIT_GRAMINE | BIT_TDX) → 0b 0000 1110 = 0x0E
// Mask = ~0x0E = 0xF1 → 0xFFF...FF1
return tag & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1;
}
}
157 changes: 62 additions & 95 deletions test/byContract/IexecPoco/IexecPoco1.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@ import {
OwnableMock__factory,
} from '../../../typechain';
import {
TAG_ALL_TEE_FRAMEWORKS,
ALL_TEE_TAGS,
TAG_BIT_2,
TAG_BIT_4,
TAG_BIT_4_AND_TEE,
TAG_NAMES,
TAG_STANDARD,
TAG_TEE,
TAG_TEE_GRAMINE,
TAG_TEE_SCONE,
TAG_TEE_TDX,
} from '../../../utils/constants';
import {
IexecOrders,
Expand Down Expand Up @@ -637,41 +637,26 @@ describe('IexecPoco1', () => {
);
});

[
{
datasetTag: TAG_TEE_SCONE,
workerpoolTag: TAG_TEE_TDX,
description: 'Scone tag (0x3) and workerpool has TDX tag (0x9)',
},
{
datasetTag: TAG_TEE_GRAMINE,
workerpoolTag: TAG_TEE_TDX,
description: 'Gramine tag (0x5) and workerpool has TDX tag (0x9)',
},
{
datasetTag: TAG_TEE_TDX,
workerpoolTag: TAG_TEE_SCONE,
description: 'TDX tag (0x9) and workerpool has Scone tag (0x3)',
},
{
datasetTag: TAG_ALL_TEE_FRAMEWORKS,
workerpoolTag: TAG_TEE,
description: 'all TEE framework bits (0xF) and workerpool has TEE only (0x1)',
},
].forEach(({ datasetTag, workerpoolTag, description }) => {
it(`Should match orders when dataset has ${description}`, async () => {
orders.dataset.tag = datasetTag;
orders.workerpool.tag = workerpoolTag;
orders.app.tag = TAG_TEE;
orders.requester.tag = TAG_TEE;

await depositForRequesterAndSchedulerWithDefaultPrices(volume);
await signOrders(iexecWrapper.getDomain(), orders, ordersActors);

await expect(iexecPocoAsRequester.matchOrders(...orders.toArray())).to.emit(
iexecPoco,
'OrdersMatched',
);
/**
* Successful match orders with all combinations of TEE tags in dataset and workerpool orders.
* Validates ignored dataset tag bits.
*/
ALL_TEE_TAGS.forEach((datasetTag) => {
ALL_TEE_TAGS.forEach((workerpoolTag) => {
it(`Should match orders with compatible TEE tags [dataset:${TAG_NAMES[datasetTag]}, workerpool:${TAG_NAMES[workerpoolTag]}]`, async () => {
orders.dataset.tag = datasetTag;
orders.workerpool.tag = workerpoolTag;
orders.app.tag = TAG_TEE;
orders.requester.tag = TAG_TEE;

await depositForRequesterAndSchedulerWithDefaultPrices(volume);
await signOrders(iexecWrapper.getDomain(), orders, ordersActors);

await expect(iexecPocoAsRequester.matchOrders(...orders.toArray())).to.emit(
iexecPoco,
'OrdersMatched',
);
});
});
});

Expand Down Expand Up @@ -1163,6 +1148,46 @@ describe('IexecPoco1', () => {
).to.not.be.reverted;
});

/**
* Successful compatibility check with all combinations of TEE tags in deal and dataset order.
* Validates ignored dataset tag bits.
*/
ALL_TEE_TAGS.forEach((datasetTag) => {
ALL_TEE_TAGS.forEach((dealTag) => {
it(`Should not revert with compatible TEE tags [dataset:${TAG_NAMES[datasetTag]}, deal:${TAG_NAMES[dealTag]}]`, async () => {
// Create a new deal with the specified tag, don't reuse the one from beforeEach.
const newDealOrders = buildOrders({
assets: { ...ordersAssets, dataset: ZeroAddress },
prices: ordersPrices,
requester: requester.address,
tag: dealTag,
volume: volume,
});
// Override salts to avoid order consumption conflicts.
newDealOrders.app.salt = ethers.id('app-salt');
newDealOrders.workerpool.salt = ethers.id('workerpool-salt');
newDealOrders.requester.salt = ethers.id('requester-salt');
await depositForRequesterAndSchedulerWithDefaultPrices(volume);
await signOrders(iexecWrapper.getDomain(), newDealOrders, ordersActors);
const dealId = getDealId(iexecWrapper.getDomain(), newDealOrders.requester);
await iexecPocoAsRequester
.matchOrders(...newDealOrders.toArray())
.then((tx) => tx.wait());
// Create dataset order with the specified tag
const datasetOrder = {
...compatibleDatasetOrder,
tag: datasetTag,
salt: ethers.id('dataset-salt'),
};
await signOrder(iexecWrapper.getDomain(), datasetOrder, datasetProvider);

// Should not revert because bits in positions 1 to 3 of the dataset order tag are ignored.
await expect(iexecPoco.assertDatasetDealCompatibility(datasetOrder, dealId)).to
.not.be.reverted;
});
});
});

it('Should revert when the dataset order is revoked or fully consumed', async () => {
await signOrder(iexecWrapper.getDomain(), compatibleDatasetOrder, datasetProvider);
// Revoke order on-chain.
Expand Down Expand Up @@ -1318,64 +1343,6 @@ describe('IexecPoco1', () => {
.withArgs('Tag compatibility not satisfied');
});

// TODO: Add more test cases for tag compatibility
[
{
datasetTag: TAG_TEE_SCONE,
dealTag: TAG_TEE_TDX,
description: 'Scone tag (0x3) and deal has TDX tag (0x9)',
saltPrefix: 'scone-tdx',
},
{
datasetTag: TAG_TEE_GRAMINE,
dealTag: TAG_TEE_TDX,
description: 'Gramine tag (0x5) and deal has TDX tag (0x9)',
saltPrefix: 'gramine-tdx',
},
{
datasetTag: TAG_TEE_TDX,
dealTag: TAG_TEE_SCONE,
description: 'TDX tag (0x9) and deal has Scone tag (0x3)',
saltPrefix: 'tdx-scone',
},
{
datasetTag: TAG_ALL_TEE_FRAMEWORKS,
dealTag: TAG_TEE,
description: 'all TEE framework bits (0xF) and deal has TEE only (0x1)',
saltPrefix: 'all-frameworks-tee',
},
].forEach(({ datasetTag, dealTag, description, saltPrefix }) => {
it(`Should not revert when dataset has ${description}`, async () => {
// Create a deal with the specified tag
const dealOrders = buildOrders({
assets: { ...ordersAssets, dataset: ZeroAddress },
prices: ordersPrices,
requester: requester.address,
tag: dealTag,
volume: volume,
});
dealOrders.app.salt = ethers.id(`${saltPrefix}-app-salt`);
dealOrders.workerpool.salt = ethers.id(`${saltPrefix}-workerpool-salt`);
dealOrders.requester.salt = ethers.id(`${saltPrefix}-requester-salt`);
await depositForRequesterAndSchedulerWithDefaultPrices(volume);
await signOrders(iexecWrapper.getDomain(), dealOrders, ordersActors);
const dealId = getDealId(iexecWrapper.getDomain(), dealOrders.requester);
await iexecPocoAsRequester.matchOrders(...dealOrders.toArray());

// Create dataset order with the specified tag
const datasetOrder = {
...compatibleDatasetOrder,
tag: datasetTag,
salt: ethers.id(`${saltPrefix}-dataset-salt`),
};
await signOrder(iexecWrapper.getDomain(), datasetOrder, datasetProvider);

// Should not revert because bits 1-3 of dataset tag are ignored
await expect(iexecPoco.assertDatasetDealCompatibility(datasetOrder, dealId)).to.not
.be.reverted;
});
});

it('Should revert when dataset has bit 4 set (not masked) and deal does not', async () => {
// Create a deal with TEE only (0b0001 = 0x1)
const teeOnlyOrders = buildOrders({
Expand Down
26 changes: 18 additions & 8 deletions utils/constants.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
// SPDX-FileCopyrightText: 2020-2025 IEXEC BLOCKCHAIN TECH <[email protected]>
// SPDX-License-Identifier: Apache-2.0

/**
* Tag constants:
* - Bit 0: TEE
* - Bit 1: Scone
* - Bit 2: Gramine
* - Bit 3: TDX
* - Bit 4: GPU (0x10)
*/
// Tag constants.
// For TEE bits positions, see IexecPoco1Facet.sol#_ignoreTeeFramework
export const TAG_STANDARD = '0x0000000000000000000000000000000000000000000000000000000000000000';
export const TAG_TEE = '0x0000000000000000000000000000000000000000000000000000000000000001';
export const TAG_TEE_SCONE = '0x0000000000000000000000000000000000000000000000000000000000000003'; // 0b0011 = TEE + Scone
Expand All @@ -20,6 +14,22 @@ export const TAG_BIT_2 = '0x0000000000000000000000000000000000000000000000000000
export const TAG_BIT_4 = '0x0000000000000000000000000000000000000000000000000000000000000010'; // 0b10000 (bit 4 in 0-indexed)
export const TAG_BIT_4_AND_TEE =
'0x0000000000000000000000000000000000000000000000000000000000000011'; // 0b10001
// Tag helpers:
export const ALL_TEE_TAGS = [
TAG_TEE,
TAG_TEE_SCONE,
TAG_TEE_GRAMINE,
TAG_TEE_TDX,
TAG_ALL_TEE_FRAMEWORKS,
];
export const TAG_NAMES: { [key: string]: string } = {
[TAG_STANDARD]: 'STANDARD',
[TAG_TEE]: 'TEE',
[TAG_TEE_SCONE]: 'TEE_SCONE',
[TAG_TEE_GRAMINE]: 'TEE_GRAMINE',
[TAG_TEE_TDX]: 'TEE_TDX',
[TAG_ALL_TEE_FRAMEWORKS]: 'TEE_ALL',
};

export const NULL = {
BYTES32: '0x0000000000000000000000000000000000000000000000000000000000000000',
Expand Down
Loading