@callcium/sdk 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +18 -18
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +575 -343
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -172,54 +172,71 @@ function lookupScope(code) {
|
|
|
172
172
|
if (!info) throw new CallciumError("INVALID_SCOPE", `Unknown scope value ${code}`);
|
|
173
173
|
return info;
|
|
174
174
|
}
|
|
175
|
+
/** ABI type code ranges and sentinel values for the descriptor format. */
|
|
176
|
+
const TypeCode = {
|
|
177
|
+
UINT_MIN: 0,
|
|
178
|
+
UINT_MAX: 31,
|
|
179
|
+
INT_MIN: 32,
|
|
180
|
+
INT_MAX: 63,
|
|
181
|
+
ADDRESS: 64,
|
|
182
|
+
BOOL: 65,
|
|
183
|
+
FUNCTION: 66,
|
|
184
|
+
FIXED_BYTES_MIN: 80,
|
|
185
|
+
FIXED_BYTES_MAX: 111,
|
|
186
|
+
BYTES: 112,
|
|
187
|
+
STRING: 113,
|
|
188
|
+
STATIC_ARRAY: 128,
|
|
189
|
+
DYNAMIC_ARRAY: 129,
|
|
190
|
+
TUPLE: 144
|
|
191
|
+
};
|
|
175
192
|
const CTX_PROP_TABLE = [
|
|
176
193
|
{
|
|
177
194
|
key: "MSG_SENDER",
|
|
178
195
|
code: 0,
|
|
179
196
|
label: "msg.sender",
|
|
180
|
-
typeCode:
|
|
197
|
+
typeCode: TypeCode.ADDRESS
|
|
181
198
|
},
|
|
182
199
|
{
|
|
183
200
|
key: "MSG_VALUE",
|
|
184
201
|
code: 1,
|
|
185
202
|
label: "msg.value",
|
|
186
|
-
typeCode:
|
|
203
|
+
typeCode: TypeCode.UINT_MAX
|
|
187
204
|
},
|
|
188
205
|
{
|
|
189
206
|
key: "BLOCK_TIMESTAMP",
|
|
190
207
|
code: 2,
|
|
191
208
|
label: "block.timestamp",
|
|
192
|
-
typeCode:
|
|
209
|
+
typeCode: TypeCode.UINT_MAX
|
|
193
210
|
},
|
|
194
211
|
{
|
|
195
212
|
key: "BLOCK_NUMBER",
|
|
196
213
|
code: 3,
|
|
197
214
|
label: "block.number",
|
|
198
|
-
typeCode:
|
|
215
|
+
typeCode: TypeCode.UINT_MAX
|
|
199
216
|
},
|
|
200
217
|
{
|
|
201
218
|
key: "CHAIN_ID",
|
|
202
219
|
code: 4,
|
|
203
220
|
label: "block.chainid",
|
|
204
|
-
typeCode:
|
|
221
|
+
typeCode: TypeCode.UINT_MAX
|
|
205
222
|
},
|
|
206
223
|
{
|
|
207
224
|
key: "TX_ORIGIN",
|
|
208
225
|
code: 5,
|
|
209
226
|
label: "tx.origin",
|
|
210
|
-
typeCode:
|
|
227
|
+
typeCode: TypeCode.ADDRESS
|
|
211
228
|
},
|
|
212
229
|
{
|
|
213
230
|
key: "BASE_FEE",
|
|
214
231
|
code: 6,
|
|
215
232
|
label: "block.basefee",
|
|
216
|
-
typeCode:
|
|
233
|
+
typeCode: TypeCode.UINT_MAX
|
|
217
234
|
},
|
|
218
235
|
{
|
|
219
236
|
key: "GAS_PRICE",
|
|
220
237
|
code: 7,
|
|
221
238
|
label: "tx.gasprice",
|
|
222
|
-
typeCode:
|
|
239
|
+
typeCode: TypeCode.UINT_MAX
|
|
223
240
|
}
|
|
224
241
|
];
|
|
225
242
|
/** Well-known context property IDs for context-scope rules. */
|
|
@@ -426,23 +443,6 @@ function lookupQuantifier(code) {
|
|
|
426
443
|
if (!info) throw new CallciumError("INVALID_QUANTIFIER", `Unknown quantifier step 0x${code.toString(16)}`);
|
|
427
444
|
return info;
|
|
428
445
|
}
|
|
429
|
-
/** ABI type code ranges and sentinel values for the descriptor format. */
|
|
430
|
-
const TypeCode = {
|
|
431
|
-
UINT_MIN: 0,
|
|
432
|
-
UINT_MAX: 31,
|
|
433
|
-
INT_MIN: 32,
|
|
434
|
-
INT_MAX: 63,
|
|
435
|
-
ADDRESS: 64,
|
|
436
|
-
BOOL: 65,
|
|
437
|
-
FUNCTION: 66,
|
|
438
|
-
FIXED_BYTES_MIN: 80,
|
|
439
|
-
FIXED_BYTES_MAX: 111,
|
|
440
|
-
BYTES: 112,
|
|
441
|
-
STRING: 113,
|
|
442
|
-
STATIC_ARRAY: 128,
|
|
443
|
-
DYNAMIC_ARRAY: 129,
|
|
444
|
-
TUPLE: 144
|
|
445
|
-
};
|
|
446
446
|
/** Throw an UNKNOWN_TYPE_CODE error. */
|
|
447
447
|
function unknownTypeCode(code) {
|
|
448
448
|
throw new CallciumError("UNKNOWN_TYPE_CODE", `Unknown type code 0x${code.toString(16).padStart(2, "0")}`);
|
|
@@ -1457,6 +1457,13 @@ function isSigned(typeCode) {
|
|
|
1457
1457
|
return typeCode >= TypeCode.INT_MIN && typeCode <= TypeCode.INT_MAX;
|
|
1458
1458
|
}
|
|
1459
1459
|
/**
|
|
1460
|
+
* Return true when the type code represents a left-aligned type (fixed bytes or function),
|
|
1461
|
+
* whose value occupies the high bytes of the word with zero padding below.
|
|
1462
|
+
*/
|
|
1463
|
+
function isLeftAligned(typeCode) {
|
|
1464
|
+
return typeCode >= TypeCode.FIXED_BYTES_MIN && typeCode <= TypeCode.FIXED_BYTES_MAX || typeCode === TypeCode.FUNCTION;
|
|
1465
|
+
}
|
|
1466
|
+
/**
|
|
1460
1467
|
* Canonicalize a raw 256-bit calldata word to its ABI value for the declared type (Policy Spec §7.8).
|
|
1461
1468
|
* A scalar loaded from untrusted calldata may carry bits outside the declared width; masking
|
|
1462
1469
|
* unsigned/address/bool/function/bytesN to width and sign-extending signed integers makes the
|
|
@@ -1490,6 +1497,15 @@ function isLengthOp(opCode) {
|
|
|
1490
1497
|
const base = opCode & ~Op.NOT;
|
|
1491
1498
|
return base >= Op.LENGTH_EQ && base <= Op.LENGTH_BETWEEN;
|
|
1492
1499
|
}
|
|
1500
|
+
/** Map a LENGTH_* opcode base to its core value-comparison twin. */
|
|
1501
|
+
function lengthToValueOp(base) {
|
|
1502
|
+
if (base === Op.LENGTH_EQ) return Op.EQ;
|
|
1503
|
+
if (base === Op.LENGTH_GT) return Op.GT;
|
|
1504
|
+
if (base === Op.LENGTH_LT) return Op.LT;
|
|
1505
|
+
if (base === Op.LENGTH_GTE) return Op.GTE;
|
|
1506
|
+
if (base === Op.LENGTH_LTE) return Op.LTE;
|
|
1507
|
+
return Op.BETWEEN;
|
|
1508
|
+
}
|
|
1493
1509
|
/** Return true when LENGTH_* operators are valid for the given type code (bytes, string, or dynamic array). */
|
|
1494
1510
|
function isLengthValidType(typeCode) {
|
|
1495
1511
|
return typeCode === TypeCode.BYTES || typeCode === TypeCode.STRING || typeCode === TypeCode.DYNAMIC_ARRAY;
|
|
@@ -1529,7 +1545,12 @@ function isIn(value, data) {
|
|
|
1529
1545
|
*/
|
|
1530
1546
|
function applyOperator(opCode, value, valueLength, operandData, typeCode) {
|
|
1531
1547
|
const negate = (opCode & Op.NOT) !== 0;
|
|
1532
|
-
|
|
1548
|
+
let base = opCode & ~Op.NOT;
|
|
1549
|
+
if (isLengthOp(base)) {
|
|
1550
|
+
value = BigInt(valueLength);
|
|
1551
|
+
typeCode = TypeCode.UINT_MAX;
|
|
1552
|
+
base = lengthToValueOp(base);
|
|
1553
|
+
}
|
|
1533
1554
|
let result;
|
|
1534
1555
|
switch (base) {
|
|
1535
1556
|
case Op.EQ:
|
|
@@ -1561,32 +1582,14 @@ function applyOperator(opCode, value, valueLength, operandData, typeCode) {
|
|
|
1561
1582
|
result = (value & mask) === mask;
|
|
1562
1583
|
break;
|
|
1563
1584
|
}
|
|
1564
|
-
case Op.BITMASK_ANY:
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
case Op.BITMASK_NONE:
|
|
1568
|
-
result = (value & toBigInt(operandData, 0)) === 0n;
|
|
1569
|
-
break;
|
|
1570
|
-
case Op.LENGTH_EQ:
|
|
1571
|
-
result = BigInt(valueLength) === toBigInt(operandData, 0);
|
|
1572
|
-
break;
|
|
1573
|
-
case Op.LENGTH_GT:
|
|
1574
|
-
result = BigInt(valueLength) > toBigInt(operandData, 0);
|
|
1575
|
-
break;
|
|
1576
|
-
case Op.LENGTH_LT:
|
|
1577
|
-
result = BigInt(valueLength) < toBigInt(operandData, 0);
|
|
1578
|
-
break;
|
|
1579
|
-
case Op.LENGTH_GTE:
|
|
1580
|
-
result = BigInt(valueLength) >= toBigInt(operandData, 0);
|
|
1581
|
-
break;
|
|
1582
|
-
case Op.LENGTH_LTE:
|
|
1583
|
-
result = BigInt(valueLength) <= toBigInt(operandData, 0);
|
|
1585
|
+
case Op.BITMASK_ANY: {
|
|
1586
|
+
const mask = toBigInt(operandData, 0);
|
|
1587
|
+
result = (value & mask) !== 0n;
|
|
1584
1588
|
break;
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
const
|
|
1588
|
-
|
|
1589
|
-
result = length >= min && length <= max;
|
|
1589
|
+
}
|
|
1590
|
+
case Op.BITMASK_NONE: {
|
|
1591
|
+
const mask = toBigInt(operandData, 0);
|
|
1592
|
+
result = (value & mask) === 0n;
|
|
1590
1593
|
break;
|
|
1591
1594
|
}
|
|
1592
1595
|
default: throw new CallciumError("INVALID_OPERATOR", `Unknown operator code 0x${base.toString(16).padStart(2, "0")}.`);
|
|
@@ -1596,189 +1599,394 @@ function applyOperator(opCode, value, valueLength, operandData, typeCode) {
|
|
|
1596
1599
|
//#endregion
|
|
1597
1600
|
//#region src/validation-issue.ts
|
|
1598
1601
|
const ZERO = `0x${"0".repeat(64)}`;
|
|
1599
|
-
/**
|
|
1600
|
-
function
|
|
1602
|
+
/** Create an issue from operator compatibility results. */
|
|
1603
|
+
function fromOpRule(code, message, groupIndex, constraintIndex, opCode) {
|
|
1601
1604
|
return {
|
|
1602
|
-
severity,
|
|
1603
|
-
category,
|
|
1605
|
+
severity: "error",
|
|
1606
|
+
category: "typeMismatch",
|
|
1604
1607
|
groupIndex,
|
|
1605
1608
|
constraintIndex,
|
|
1606
1609
|
code,
|
|
1607
|
-
value1,
|
|
1608
|
-
value2,
|
|
1610
|
+
value1: opCode,
|
|
1611
|
+
value2: ZERO,
|
|
1609
1612
|
message
|
|
1610
1613
|
};
|
|
1611
1614
|
}
|
|
1612
|
-
/**
|
|
1613
|
-
function
|
|
1614
|
-
return
|
|
1615
|
+
/** Issue for an operand that is not canonically encoded for the declared type. */
|
|
1616
|
+
function nonCanonicalOperand(groupIndex, constraintIndex, operand, canonical) {
|
|
1617
|
+
return {
|
|
1618
|
+
severity: "error",
|
|
1619
|
+
category: "typeMismatch",
|
|
1620
|
+
groupIndex,
|
|
1621
|
+
constraintIndex,
|
|
1622
|
+
code: "NON_CANONICAL_OPERAND",
|
|
1623
|
+
value1: operand,
|
|
1624
|
+
value2: canonical,
|
|
1625
|
+
message: "Operand is not the canonical encoding for the declared type"
|
|
1626
|
+
};
|
|
1615
1627
|
}
|
|
1616
|
-
/** eq(v)
|
|
1617
|
-
function eqNeqContradiction(groupIndex, constraintIndex, value) {
|
|
1618
|
-
return
|
|
1628
|
+
/** eq(v)/neq(v) or lengthEq(v)/lengthNeq(v) on the same path. */
|
|
1629
|
+
function eqNeqContradiction(isLength, groupIndex, constraintIndex, value) {
|
|
1630
|
+
return {
|
|
1631
|
+
severity: "error",
|
|
1632
|
+
category: "contradiction",
|
|
1633
|
+
groupIndex,
|
|
1634
|
+
constraintIndex,
|
|
1635
|
+
code: isLength ? "LENGTH_EQ_NEQ_CONTRADICTION" : "EQ_NEQ_CONTRADICTION",
|
|
1636
|
+
value1: value,
|
|
1637
|
+
value2: ZERO,
|
|
1638
|
+
message: isLength ? "lengthEq(v) and lengthNeq(v) on same path" : "eq(v) and neq(v) on same path"
|
|
1639
|
+
};
|
|
1619
1640
|
}
|
|
1620
|
-
/** Multiple eq() operators with different values. */
|
|
1621
|
-
function conflictingEquality(groupIndex, constraintIndex, existing, newValue) {
|
|
1622
|
-
return
|
|
1641
|
+
/** Multiple eq()/lengthEq() operators with different values. */
|
|
1642
|
+
function conflictingEquality(isLength, groupIndex, constraintIndex, existing, newValue) {
|
|
1643
|
+
return {
|
|
1644
|
+
severity: "error",
|
|
1645
|
+
category: "contradiction",
|
|
1646
|
+
groupIndex,
|
|
1647
|
+
constraintIndex,
|
|
1648
|
+
code: isLength ? "CONFLICTING_LENGTH" : "CONFLICTING_EQUALITY",
|
|
1649
|
+
value1: existing,
|
|
1650
|
+
value2: newValue,
|
|
1651
|
+
message: isLength ? "Multiple lengthEq() operators with different values" : "Multiple eq() operators with different values"
|
|
1652
|
+
};
|
|
1623
1653
|
}
|
|
1624
|
-
/** Value is outside the physical range of
|
|
1625
|
-
function outOfPhysicalBounds(groupIndex, constraintIndex, value) {
|
|
1626
|
-
return
|
|
1654
|
+
/** Value/length is outside the physical range of its type. */
|
|
1655
|
+
function outOfPhysicalBounds(isLength, groupIndex, constraintIndex, value) {
|
|
1656
|
+
return {
|
|
1657
|
+
severity: "error",
|
|
1658
|
+
category: "contradiction",
|
|
1659
|
+
groupIndex,
|
|
1660
|
+
constraintIndex,
|
|
1661
|
+
code: isLength ? "OUT_OF_PHYSICAL_LENGTH_BOUNDS" : "OUT_OF_PHYSICAL_BOUNDS",
|
|
1662
|
+
value1: value,
|
|
1663
|
+
value2: ZERO,
|
|
1664
|
+
message: isLength ? "Length value is outside the physical range" : "Value is outside the physical range of the type"
|
|
1665
|
+
};
|
|
1627
1666
|
}
|
|
1628
|
-
/** gt() on
|
|
1629
|
-
function impossibleGt(groupIndex, constraintIndex, value) {
|
|
1630
|
-
return
|
|
1667
|
+
/** gt()/lengthGt() on the maximum is impossible. */
|
|
1668
|
+
function impossibleGt(isLength, groupIndex, constraintIndex, value) {
|
|
1669
|
+
return {
|
|
1670
|
+
severity: "error",
|
|
1671
|
+
category: "contradiction",
|
|
1672
|
+
groupIndex,
|
|
1673
|
+
constraintIndex,
|
|
1674
|
+
code: isLength ? "IMPOSSIBLE_LENGTH_GT" : "IMPOSSIBLE_GT",
|
|
1675
|
+
value1: value,
|
|
1676
|
+
value2: ZERO,
|
|
1677
|
+
message: isLength ? "lengthGt() on maximum length is impossible" : "gt() on type maximum is impossible"
|
|
1678
|
+
};
|
|
1631
1679
|
}
|
|
1632
|
-
/** lt() on
|
|
1633
|
-
function impossibleLt(groupIndex, constraintIndex, value) {
|
|
1634
|
-
return
|
|
1680
|
+
/** lt()/lengthLt() on the minimum is impossible. */
|
|
1681
|
+
function impossibleLt(isLength, groupIndex, constraintIndex, value) {
|
|
1682
|
+
return {
|
|
1683
|
+
severity: "error",
|
|
1684
|
+
category: "contradiction",
|
|
1685
|
+
groupIndex,
|
|
1686
|
+
constraintIndex,
|
|
1687
|
+
code: isLength ? "IMPOSSIBLE_LENGTH_LT" : "IMPOSSIBLE_LT",
|
|
1688
|
+
value1: value,
|
|
1689
|
+
value2: ZERO,
|
|
1690
|
+
message: isLength ? "lengthLt() on minimum length is impossible" : "lt() on type minimum is impossible"
|
|
1691
|
+
};
|
|
1635
1692
|
}
|
|
1636
|
-
/** eq() value is excluded by bound. */
|
|
1637
|
-
function boundsExcludeEquality(groupIndex, constraintIndex, eq, bound) {
|
|
1638
|
-
return
|
|
1693
|
+
/** eq()/lengthEq() value is excluded by a bound. */
|
|
1694
|
+
function boundsExcludeEquality(isLength, groupIndex, constraintIndex, eq, bound) {
|
|
1695
|
+
return {
|
|
1696
|
+
severity: "error",
|
|
1697
|
+
category: "contradiction",
|
|
1698
|
+
groupIndex,
|
|
1699
|
+
constraintIndex,
|
|
1700
|
+
code: isLength ? "BOUNDS_EXCLUDE_LENGTH" : "BOUNDS_EXCLUDE_EQUALITY",
|
|
1701
|
+
value1: eq,
|
|
1702
|
+
value2: bound,
|
|
1703
|
+
message: isLength ? "lengthEq() value is excluded by bound" : "eq() value is excluded by bound"
|
|
1704
|
+
};
|
|
1639
1705
|
}
|
|
1640
|
-
/** Lower bound exceeds upper bound. */
|
|
1641
|
-
function impossibleRange(groupIndex, constraintIndex, lower, upper) {
|
|
1642
|
-
return
|
|
1706
|
+
/** Lower bound/length exceeds the upper bound. */
|
|
1707
|
+
function impossibleRange(isLength, groupIndex, constraintIndex, lower, upper) {
|
|
1708
|
+
return {
|
|
1709
|
+
severity: "error",
|
|
1710
|
+
category: "contradiction",
|
|
1711
|
+
groupIndex,
|
|
1712
|
+
constraintIndex,
|
|
1713
|
+
code: isLength ? "IMPOSSIBLE_LENGTH_RANGE" : "IMPOSSIBLE_RANGE",
|
|
1714
|
+
value1: lower,
|
|
1715
|
+
value2: upper,
|
|
1716
|
+
message: isLength ? "Lower length bound exceeds upper bound" : "Lower bound exceeds upper bound"
|
|
1717
|
+
};
|
|
1643
1718
|
}
|
|
1644
1719
|
/** Set operation excludes existing eq() value. */
|
|
1645
1720
|
function setExcludesEquality(groupIndex, constraintIndex, value) {
|
|
1646
|
-
return
|
|
1721
|
+
return {
|
|
1722
|
+
severity: "error",
|
|
1723
|
+
category: "contradiction",
|
|
1724
|
+
groupIndex,
|
|
1725
|
+
constraintIndex,
|
|
1726
|
+
code: "SET_EXCLUDES_EQUALITY",
|
|
1727
|
+
value1: value,
|
|
1728
|
+
value2: ZERO,
|
|
1729
|
+
message: "Set operation excludes existing eq() value"
|
|
1730
|
+
};
|
|
1647
1731
|
}
|
|
1648
1732
|
/** Multiple isIn() sets have no intersection. */
|
|
1649
1733
|
function emptySetIntersection(groupIndex, constraintIndex) {
|
|
1650
|
-
return
|
|
1734
|
+
return {
|
|
1735
|
+
severity: "error",
|
|
1736
|
+
category: "contradiction",
|
|
1737
|
+
groupIndex,
|
|
1738
|
+
constraintIndex,
|
|
1739
|
+
code: "EMPTY_SET_INTERSECTION",
|
|
1740
|
+
value1: ZERO,
|
|
1741
|
+
value2: ZERO,
|
|
1742
|
+
message: "Multiple isIn() sets have no intersection"
|
|
1743
|
+
};
|
|
1651
1744
|
}
|
|
1652
1745
|
/** All values in isIn() set are excluded by neq/notIn. */
|
|
1653
1746
|
function setFullyExcluded(groupIndex, constraintIndex) {
|
|
1654
|
-
return
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
}
|
|
1664
|
-
/** lengthEq() value is excluded by bound. */
|
|
1665
|
-
function boundsExcludeLength(groupIndex, constraintIndex, eq, bound) {
|
|
1666
|
-
return makeIssue("error", "contradiction", groupIndex, constraintIndex, "BOUNDS_EXCLUDE_LENGTH", eq, bound, "lengthEq() value is excluded by bound");
|
|
1667
|
-
}
|
|
1668
|
-
/** Lower length bound exceeds upper bound. */
|
|
1669
|
-
function impossibleLengthRange(groupIndex, constraintIndex, lower, upper) {
|
|
1670
|
-
return makeIssue("error", "contradiction", groupIndex, constraintIndex, "IMPOSSIBLE_LENGTH_RANGE", lower, upper, "Lower length bound exceeds upper bound");
|
|
1671
|
-
}
|
|
1672
|
-
/** Length value is outside the physical range. */
|
|
1673
|
-
function outOfPhysicalLengthBounds(groupIndex, constraintIndex, value) {
|
|
1674
|
-
return makeIssue("error", "contradiction", groupIndex, constraintIndex, "OUT_OF_PHYSICAL_LENGTH_BOUNDS", value, ZERO, "Length value is outside the physical range");
|
|
1675
|
-
}
|
|
1676
|
-
/** lengthGt() on maximum length is impossible. */
|
|
1677
|
-
function impossibleLengthGt(groupIndex, constraintIndex, value) {
|
|
1678
|
-
return makeIssue("error", "contradiction", groupIndex, constraintIndex, "IMPOSSIBLE_LENGTH_GT", value, ZERO, "lengthGt() on maximum length is impossible");
|
|
1679
|
-
}
|
|
1680
|
-
/** lengthLt() on minimum length is impossible. */
|
|
1681
|
-
function impossibleLengthLt(groupIndex, constraintIndex, value) {
|
|
1682
|
-
return makeIssue("error", "contradiction", groupIndex, constraintIndex, "IMPOSSIBLE_LENGTH_LT", value, ZERO, "lengthLt() on minimum length is impossible");
|
|
1747
|
+
return {
|
|
1748
|
+
severity: "error",
|
|
1749
|
+
category: "contradiction",
|
|
1750
|
+
groupIndex,
|
|
1751
|
+
constraintIndex,
|
|
1752
|
+
code: "SET_FULLY_EXCLUDED",
|
|
1753
|
+
value1: ZERO,
|
|
1754
|
+
value2: ZERO,
|
|
1755
|
+
message: "All values in isIn() set are excluded by neq/notIn"
|
|
1756
|
+
};
|
|
1683
1757
|
}
|
|
1684
1758
|
/** Bitmask operators conflict. */
|
|
1685
1759
|
function bitmaskContradiction(groupIndex, constraintIndex, mask, conflicting) {
|
|
1686
|
-
return
|
|
1760
|
+
return {
|
|
1761
|
+
severity: "error",
|
|
1762
|
+
category: "contradiction",
|
|
1763
|
+
groupIndex,
|
|
1764
|
+
constraintIndex,
|
|
1765
|
+
code: "BITMASK_CONTRADICTION",
|
|
1766
|
+
value1: mask,
|
|
1767
|
+
value2: conflicting,
|
|
1768
|
+
message: "Bitmask operators conflict"
|
|
1769
|
+
};
|
|
1687
1770
|
}
|
|
1688
1771
|
/** bitmaskAny is impossible because all bits are forbidden. */
|
|
1689
1772
|
function bitmaskAnyImpossible(groupIndex, constraintIndex, mask, mustBeZero) {
|
|
1690
|
-
return
|
|
1773
|
+
return {
|
|
1774
|
+
severity: "error",
|
|
1775
|
+
category: "contradiction",
|
|
1776
|
+
groupIndex,
|
|
1777
|
+
constraintIndex,
|
|
1778
|
+
code: "BITMASK_ANY_IMPOSSIBLE",
|
|
1779
|
+
value1: mask,
|
|
1780
|
+
value2: mustBeZero,
|
|
1781
|
+
message: "bitmaskAny is impossible because all bits are forbidden"
|
|
1782
|
+
};
|
|
1691
1783
|
}
|
|
1692
1784
|
/** isIn/notIn set is not strictly sorted and deduplicated. */
|
|
1693
1785
|
function unsortedInSet(groupIndex, constraintIndex) {
|
|
1694
|
-
return
|
|
1786
|
+
return {
|
|
1787
|
+
severity: "error",
|
|
1788
|
+
category: "contradiction",
|
|
1789
|
+
groupIndex,
|
|
1790
|
+
constraintIndex,
|
|
1791
|
+
code: "UNSORTED_IN_SET",
|
|
1792
|
+
value1: ZERO,
|
|
1793
|
+
value2: ZERO,
|
|
1794
|
+
message: "isIn/notIn set is not strictly sorted and deduplicated."
|
|
1795
|
+
};
|
|
1695
1796
|
}
|
|
1696
1797
|
/** Group contains zero constraints. */
|
|
1697
1798
|
function emptyGroup(groupIndex) {
|
|
1698
|
-
return
|
|
1799
|
+
return {
|
|
1800
|
+
severity: "error",
|
|
1801
|
+
category: "vacuity",
|
|
1802
|
+
groupIndex,
|
|
1803
|
+
constraintIndex: 0,
|
|
1804
|
+
code: "EMPTY_GROUP",
|
|
1805
|
+
value1: ZERO,
|
|
1806
|
+
value2: ZERO,
|
|
1807
|
+
message: "Group contains zero constraints."
|
|
1808
|
+
};
|
|
1699
1809
|
}
|
|
1700
|
-
/** Numeric bound
|
|
1701
|
-
function dominatedBound(groupIndex, constraintIndex, value) {
|
|
1702
|
-
return
|
|
1810
|
+
/** Numeric/length bound dominated by a stricter bound. */
|
|
1811
|
+
function dominatedBound(isLength, groupIndex, constraintIndex, value) {
|
|
1812
|
+
return {
|
|
1813
|
+
severity: "warning",
|
|
1814
|
+
category: "redundancy",
|
|
1815
|
+
groupIndex,
|
|
1816
|
+
constraintIndex,
|
|
1817
|
+
code: isLength ? "DOMINATED_LENGTH_BOUND" : "DOMINATED_BOUND",
|
|
1818
|
+
value1: value,
|
|
1819
|
+
value2: ZERO,
|
|
1820
|
+
message: isLength ? "Length bound is dominated by a stricter bound" : "Numeric bound is dominated by a stricter bound"
|
|
1821
|
+
};
|
|
1703
1822
|
}
|
|
1704
|
-
/** Bound
|
|
1705
|
-
function redundantBound(groupIndex, constraintIndex, bound, eq) {
|
|
1706
|
-
return
|
|
1823
|
+
/** Bound redundant because eq()/lengthEq() is set. */
|
|
1824
|
+
function redundantBound(isLength, groupIndex, constraintIndex, bound, eq) {
|
|
1825
|
+
return {
|
|
1826
|
+
severity: "warning",
|
|
1827
|
+
category: "redundancy",
|
|
1828
|
+
groupIndex,
|
|
1829
|
+
constraintIndex,
|
|
1830
|
+
code: isLength ? "REDUNDANT_LENGTH_BOUND" : "REDUNDANT_BOUND",
|
|
1831
|
+
value1: bound,
|
|
1832
|
+
value2: eq,
|
|
1833
|
+
message: isLength ? "Length bound is redundant because lengthEq() is set" : "Bound is redundant because eq() is set"
|
|
1834
|
+
};
|
|
1707
1835
|
}
|
|
1708
1836
|
/** notIn() value was present in isIn() set. */
|
|
1709
1837
|
function setReduction(groupIndex, constraintIndex, value) {
|
|
1710
|
-
return
|
|
1838
|
+
return {
|
|
1839
|
+
severity: "warning",
|
|
1840
|
+
category: "redundancy",
|
|
1841
|
+
groupIndex,
|
|
1842
|
+
constraintIndex,
|
|
1843
|
+
code: "SET_REDUCTION",
|
|
1844
|
+
value1: value,
|
|
1845
|
+
value2: ZERO,
|
|
1846
|
+
message: "notIn() value was present in isIn() set"
|
|
1847
|
+
};
|
|
1711
1848
|
}
|
|
1712
1849
|
/** isIn() sets partially overlap. */
|
|
1713
1850
|
function setRedundancy(groupIndex, constraintIndex, intersectionCount) {
|
|
1714
|
-
return
|
|
1851
|
+
return {
|
|
1852
|
+
severity: "warning",
|
|
1853
|
+
category: "redundancy",
|
|
1854
|
+
groupIndex,
|
|
1855
|
+
constraintIndex,
|
|
1856
|
+
code: "SET_REDUNDANCY",
|
|
1857
|
+
value1: intersectionCount,
|
|
1858
|
+
value2: ZERO,
|
|
1859
|
+
message: "isIn() sets partially overlap"
|
|
1860
|
+
};
|
|
1715
1861
|
}
|
|
1716
1862
|
/** Some values in isIn() set are excluded by neq/notIn. */
|
|
1717
1863
|
function setPartiallyExcluded(groupIndex, constraintIndex, excludedCount) {
|
|
1718
|
-
return
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1864
|
+
return {
|
|
1865
|
+
severity: "warning",
|
|
1866
|
+
category: "redundancy",
|
|
1867
|
+
groupIndex,
|
|
1868
|
+
constraintIndex,
|
|
1869
|
+
code: "SET_PARTIALLY_EXCLUDED",
|
|
1870
|
+
value1: excludedCount,
|
|
1871
|
+
value2: ZERO,
|
|
1872
|
+
message: "Some values in isIn() set are excluded by neq/notIn"
|
|
1873
|
+
};
|
|
1727
1874
|
}
|
|
1728
1875
|
/** Bitmask operation is redundant. */
|
|
1729
1876
|
function redundantBitmask(groupIndex, constraintIndex, mask, existing) {
|
|
1730
|
-
return
|
|
1877
|
+
return {
|
|
1878
|
+
severity: "warning",
|
|
1879
|
+
category: "redundancy",
|
|
1880
|
+
groupIndex,
|
|
1881
|
+
constraintIndex,
|
|
1882
|
+
code: "REDUNDANT_BITMASK",
|
|
1883
|
+
value1: mask,
|
|
1884
|
+
value2: existing,
|
|
1885
|
+
message: "Bitmask operation is redundant"
|
|
1886
|
+
};
|
|
1887
|
+
}
|
|
1888
|
+
/** gte/lte pair fusible into a single range operator. */
|
|
1889
|
+
function fusibleRange(isLength, groupIndex, constraintIndex, low, high) {
|
|
1890
|
+
return {
|
|
1891
|
+
severity: "warning",
|
|
1892
|
+
category: "redundancy",
|
|
1893
|
+
groupIndex,
|
|
1894
|
+
constraintIndex,
|
|
1895
|
+
code: isLength ? "FUSIBLE_LENGTH_RANGE" : "FUSIBLE_RANGE",
|
|
1896
|
+
value1: low,
|
|
1897
|
+
value2: high,
|
|
1898
|
+
message: isLength ? "lengthGte() and lengthLte() fuse into a single lengthBetween()" : "gte() and lte() fuse into a single between()"
|
|
1899
|
+
};
|
|
1731
1900
|
}
|
|
1732
1901
|
/** Duplicate operator in constraint. */
|
|
1733
1902
|
function duplicateConstraint(groupIndex, constraintIndex) {
|
|
1734
|
-
return
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1903
|
+
return {
|
|
1904
|
+
severity: "warning",
|
|
1905
|
+
category: "redundancy",
|
|
1906
|
+
groupIndex,
|
|
1907
|
+
constraintIndex,
|
|
1908
|
+
code: "DUPLICATE_CONSTRAINT",
|
|
1909
|
+
value1: ZERO,
|
|
1910
|
+
value2: ZERO,
|
|
1911
|
+
message: "Duplicate operator in constraint"
|
|
1912
|
+
};
|
|
1743
1913
|
}
|
|
1744
|
-
/** lengthGte(0) is always true. */
|
|
1745
|
-
function
|
|
1746
|
-
return
|
|
1914
|
+
/** gte(min)/lengthGte(0) is always true. */
|
|
1915
|
+
function vacuousGte(isLength, groupIndex, constraintIndex, value) {
|
|
1916
|
+
return {
|
|
1917
|
+
severity: "info",
|
|
1918
|
+
category: "vacuity",
|
|
1919
|
+
groupIndex,
|
|
1920
|
+
constraintIndex,
|
|
1921
|
+
code: isLength ? "VACUOUS_LENGTH_GTE" : "VACUOUS_GTE",
|
|
1922
|
+
value1: value,
|
|
1923
|
+
value2: ZERO,
|
|
1924
|
+
message: isLength ? "lengthGte(0) is always true" : "gte() bound equals type minimum (always true)"
|
|
1925
|
+
};
|
|
1747
1926
|
}
|
|
1748
|
-
/** lengthLte()
|
|
1749
|
-
function
|
|
1750
|
-
return
|
|
1927
|
+
/** lte(max)/lengthLte(max) is always true. */
|
|
1928
|
+
function vacuousLte(isLength, groupIndex, constraintIndex, value) {
|
|
1929
|
+
return {
|
|
1930
|
+
severity: "info",
|
|
1931
|
+
category: "vacuity",
|
|
1932
|
+
groupIndex,
|
|
1933
|
+
constraintIndex,
|
|
1934
|
+
code: isLength ? "VACUOUS_LENGTH_LTE" : "VACUOUS_LTE",
|
|
1935
|
+
value1: value,
|
|
1936
|
+
value2: ZERO,
|
|
1937
|
+
message: isLength ? "lengthLte() bound equals maximum (always true)" : "lte() bound equals type maximum (always true)"
|
|
1938
|
+
};
|
|
1751
1939
|
}
|
|
1752
1940
|
/** Path deeper than the reference enforcer's cap. */
|
|
1753
1941
|
function pathDepthExceeded(groupIndex, constraintIndex, depth, maxDepth) {
|
|
1754
|
-
return
|
|
1942
|
+
return {
|
|
1943
|
+
severity: "warning",
|
|
1944
|
+
category: "compatibility",
|
|
1945
|
+
groupIndex,
|
|
1946
|
+
constraintIndex,
|
|
1947
|
+
code: "PATH_DEPTH_EXCEEDED",
|
|
1948
|
+
value1: depth,
|
|
1949
|
+
value2: maxDepth,
|
|
1950
|
+
message: "Path depth exceeds the reference enforcer cap"
|
|
1951
|
+
};
|
|
1755
1952
|
}
|
|
1756
1953
|
/** Quantifier over a static array beyond the reference enforcer's iteration cap. */
|
|
1757
1954
|
function quantifierOverStaticLimit(groupIndex, constraintIndex, arrayLength, maxLength) {
|
|
1758
|
-
return
|
|
1955
|
+
return {
|
|
1956
|
+
severity: "warning",
|
|
1957
|
+
category: "compatibility",
|
|
1958
|
+
groupIndex,
|
|
1959
|
+
constraintIndex,
|
|
1960
|
+
code: "QUANTIFIER_OVER_STATIC_LIMIT",
|
|
1961
|
+
value1: arrayLength,
|
|
1962
|
+
value2: maxLength,
|
|
1963
|
+
message: "Quantifier over static array exceeds the reference enforcer cap"
|
|
1964
|
+
};
|
|
1759
1965
|
}
|
|
1760
1966
|
/** Context property ID outside the assigned set. */
|
|
1761
1967
|
function unknownContextProperty(groupIndex, constraintIndex, contextId, maxContextId) {
|
|
1762
|
-
return
|
|
1968
|
+
return {
|
|
1969
|
+
severity: "warning",
|
|
1970
|
+
category: "compatibility",
|
|
1971
|
+
groupIndex,
|
|
1972
|
+
constraintIndex,
|
|
1973
|
+
code: "UNKNOWN_CONTEXT_PROPERTY",
|
|
1974
|
+
value1: contextId,
|
|
1975
|
+
value2: maxContextId,
|
|
1976
|
+
message: "Unknown context property ID"
|
|
1977
|
+
};
|
|
1763
1978
|
}
|
|
1764
1979
|
/** Negated operator under the existential quantifier. */
|
|
1765
1980
|
function negationUnderAny(groupIndex, constraintIndex, opCode) {
|
|
1766
|
-
return makeIssue("warning", "compatibility", groupIndex, constraintIndex, "NEGATION_UNDER_ANY", opCode, ZERO, "Negated operator under any() passes when a decoy element differs");
|
|
1767
|
-
}
|
|
1768
|
-
/** Pre-select value or length issue factories for a bound domain. */
|
|
1769
|
-
function boundIssues(isLength) {
|
|
1770
1981
|
return {
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
redundant: isLength ? redundantLengthBound : redundantBound,
|
|
1780
|
-
vacuousGte: isLength ? vacuousLengthGte : vacuousGte,
|
|
1781
|
-
vacuousLte: isLength ? vacuousLengthLte : vacuousLte
|
|
1982
|
+
severity: "warning",
|
|
1983
|
+
category: "compatibility",
|
|
1984
|
+
groupIndex,
|
|
1985
|
+
constraintIndex,
|
|
1986
|
+
code: "NEGATION_UNDER_ANY",
|
|
1987
|
+
value1: opCode,
|
|
1988
|
+
value2: ZERO,
|
|
1989
|
+
message: "Negated operator under any() passes when a decoy element differs"
|
|
1782
1990
|
};
|
|
1783
1991
|
}
|
|
1784
1992
|
//#endregion
|
|
@@ -1801,25 +2009,11 @@ const MAX_NOT_IN = 8;
|
|
|
1801
2009
|
const LENGTH_MAX = 4294967295n;
|
|
1802
2010
|
const UINT256_MAX = (1n << 256n) - 1n;
|
|
1803
2011
|
const INT256_SIGN_BIT = 1n << 255n;
|
|
1804
|
-
/**
|
|
1805
|
-
function
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
function isGt(a, b, signed) {
|
|
1810
|
-
return signed ? toSigned(a) > toSigned(b) : a > b;
|
|
1811
|
-
}
|
|
1812
|
-
/** a >= b. */
|
|
1813
|
-
function isGte(a, b, signed) {
|
|
1814
|
-
return signed ? toSigned(a) >= toSigned(b) : a >= b;
|
|
1815
|
-
}
|
|
1816
|
-
/** a < b. */
|
|
1817
|
-
function isLt(a, b, signed) {
|
|
1818
|
-
return signed ? toSigned(a) < toSigned(b) : a < b;
|
|
1819
|
-
}
|
|
1820
|
-
/** a <= b. */
|
|
1821
|
-
function isLte(a, b, signed) {
|
|
1822
|
-
return signed ? toSigned(a) <= toSigned(b) : a <= b;
|
|
2012
|
+
/** Signed-aware difference of two raw uint256 values: negative if a<b, zero if equal, positive if a>b. */
|
|
2013
|
+
function signedCompare(a, b, signed) {
|
|
2014
|
+
if (!signed) return a - b;
|
|
2015
|
+
const toS = (v) => v >= INT256_SIGN_BIT ? v - (1n << 256n) : v;
|
|
2016
|
+
return toS(a) - toS(b);
|
|
1823
2017
|
}
|
|
1824
2018
|
/** True for uint and int type codes. */
|
|
1825
2019
|
function isNumericType(typeCode) {
|
|
@@ -1914,22 +2108,6 @@ function getIncompat(opBase, typeInfo) {
|
|
|
1914
2108
|
message: "Unknown operator code"
|
|
1915
2109
|
};
|
|
1916
2110
|
}
|
|
1917
|
-
/** Check operator–type compatibility and return an issue descriptor on mismatch. */
|
|
1918
|
-
function checkCompatibility(opBase, typeCode, isDynamic, staticSize) {
|
|
1919
|
-
const incompat = getIncompat(opBase, {
|
|
1920
|
-
typeCode,
|
|
1921
|
-
isDynamic,
|
|
1922
|
-
staticSize
|
|
1923
|
-
});
|
|
1924
|
-
return incompat ? {
|
|
1925
|
-
compatible: false,
|
|
1926
|
-
...incompat
|
|
1927
|
-
} : {
|
|
1928
|
-
compatible: true,
|
|
1929
|
-
code: "",
|
|
1930
|
-
message: ""
|
|
1931
|
-
};
|
|
1932
|
-
}
|
|
1933
2111
|
/**
|
|
1934
2112
|
* True if operator `opCode` is allowed on a target with the given type info.
|
|
1935
2113
|
* Tolerates the NOT flag on `opCode`.
|
|
@@ -1941,6 +2119,19 @@ function isOpAllowed(opCode, typeInfo) {
|
|
|
1941
2119
|
function readValue(opHex) {
|
|
1942
2120
|
return BigInt(`0x${opHex.slice(4)}`);
|
|
1943
2121
|
}
|
|
2122
|
+
/** First payload word deviating from the canonical encoding for the type, with its canonical form, or null. */
|
|
2123
|
+
function findNonCanonicalWord(opHex, typeCode) {
|
|
2124
|
+
const body = opHex.slice(4);
|
|
2125
|
+
for (let i = 0; i + 64 <= body.length; i += 64) {
|
|
2126
|
+
const word = BigInt(`0x${body.slice(i, i + 64)}`);
|
|
2127
|
+
const canonical = canonicalize(word, typeCode);
|
|
2128
|
+
if (canonical !== word) return {
|
|
2129
|
+
word,
|
|
2130
|
+
canonical
|
|
2131
|
+
};
|
|
2132
|
+
}
|
|
2133
|
+
return null;
|
|
2134
|
+
}
|
|
1944
2135
|
/** Read a pair of uint256 values from a BETWEEN operator. */
|
|
1945
2136
|
function readPair(opHex) {
|
|
1946
2137
|
const body = opHex.slice(4);
|
|
@@ -1979,6 +2170,23 @@ function negateBoundOp(base) {
|
|
|
1979
2170
|
if (base === Op.LTE) return Op.GT;
|
|
1980
2171
|
return base;
|
|
1981
2172
|
}
|
|
2173
|
+
/** Fresh bound domain with no constraints recorded yet. */
|
|
2174
|
+
function emptyBoundDomain(boundIsSigned, min, max) {
|
|
2175
|
+
return {
|
|
2176
|
+
isSigned: boundIsSigned,
|
|
2177
|
+
min,
|
|
2178
|
+
max,
|
|
2179
|
+
hasEq: false,
|
|
2180
|
+
eq: 0n,
|
|
2181
|
+
hasLower: false,
|
|
2182
|
+
lower: 0n,
|
|
2183
|
+
lowerInclusive: false,
|
|
2184
|
+
hasUpper: false,
|
|
2185
|
+
upper: 0n,
|
|
2186
|
+
upperInclusive: false,
|
|
2187
|
+
holes: []
|
|
2188
|
+
};
|
|
2189
|
+
}
|
|
1982
2190
|
/** Create a fresh constraint context with domain limits from the type. */
|
|
1983
2191
|
function initContext(scope, path, typeInfo) {
|
|
1984
2192
|
const { min, max } = getDomainLimits(typeInfo.typeCode);
|
|
@@ -1986,38 +2194,12 @@ function initContext(scope, path, typeInfo) {
|
|
|
1986
2194
|
scope,
|
|
1987
2195
|
path,
|
|
1988
2196
|
typeInfo,
|
|
1989
|
-
numeric:
|
|
1990
|
-
isSigned: isSigned(typeInfo.typeCode),
|
|
1991
|
-
min,
|
|
1992
|
-
max,
|
|
1993
|
-
hasEq: false,
|
|
1994
|
-
eq: 0n,
|
|
1995
|
-
hasLower: false,
|
|
1996
|
-
lower: 0n,
|
|
1997
|
-
lowerInclusive: false,
|
|
1998
|
-
hasUpper: false,
|
|
1999
|
-
upper: 0n,
|
|
2000
|
-
upperInclusive: false,
|
|
2001
|
-
holes: []
|
|
2002
|
-
},
|
|
2197
|
+
numeric: emptyBoundDomain(isSigned(typeInfo.typeCode), min, max),
|
|
2003
2198
|
bitmask: {
|
|
2004
2199
|
mustBeOne: 0n,
|
|
2005
2200
|
mustBeZero: 0n
|
|
2006
2201
|
},
|
|
2007
|
-
length:
|
|
2008
|
-
isSigned: false,
|
|
2009
|
-
min: 0n,
|
|
2010
|
-
max: LENGTH_MAX,
|
|
2011
|
-
hasEq: false,
|
|
2012
|
-
eq: 0n,
|
|
2013
|
-
hasLower: false,
|
|
2014
|
-
lower: 0n,
|
|
2015
|
-
lowerInclusive: false,
|
|
2016
|
-
hasUpper: false,
|
|
2017
|
-
upper: 0n,
|
|
2018
|
-
upperInclusive: false,
|
|
2019
|
-
holes: []
|
|
2020
|
-
},
|
|
2202
|
+
length: emptyBoundDomain(false, 0n, LENGTH_MAX),
|
|
2021
2203
|
set: {
|
|
2022
2204
|
hasIn: false,
|
|
2023
2205
|
inValues: [],
|
|
@@ -2026,33 +2208,33 @@ function initContext(scope, path, typeInfo) {
|
|
|
2026
2208
|
};
|
|
2027
2209
|
}
|
|
2028
2210
|
/** Narrow the numeric bound domain with an operator and emit issues on contradiction or redundancy. */
|
|
2029
|
-
function updateBound(domain, base, isNegated, value,
|
|
2211
|
+
function updateBound(domain, base, isNegated, value, isLength, groupIndex, constraintIndex, issues) {
|
|
2030
2212
|
let changedEq = false;
|
|
2031
2213
|
let changedLower = false;
|
|
2032
2214
|
let changedUpper = false;
|
|
2033
2215
|
if (isNegated) {
|
|
2034
2216
|
if (base === Op.EQ) {
|
|
2035
|
-
if (domain.hasEq && domain.eq === value) issues.push(
|
|
2217
|
+
if (domain.hasEq && domain.eq === value) issues.push(eqNeqContradiction(isLength, groupIndex, constraintIndex, bigintToHex(value)));
|
|
2036
2218
|
let alreadyHole = false;
|
|
2037
2219
|
for (let j = 0; j < domain.holes.length; j++) if (domain.holes[j] === value) {
|
|
2038
2220
|
alreadyHole = true;
|
|
2039
2221
|
break;
|
|
2040
2222
|
}
|
|
2041
2223
|
if (!alreadyHole && domain.holes.length < MAX_HOLES) domain.holes.push(value);
|
|
2042
|
-
} else updateBound(domain, negateBoundOp(base), false, value,
|
|
2224
|
+
} else updateBound(domain, negateBoundOp(base), false, value, isLength, groupIndex, constraintIndex, issues);
|
|
2043
2225
|
return;
|
|
2044
2226
|
}
|
|
2045
|
-
if (base === Op.GTE && value === domain.min) issues.push(
|
|
2046
|
-
else if (base === Op.LTE && value === domain.max) issues.push(
|
|
2047
|
-
if (
|
|
2048
|
-
else if (base === Op.GT && value === domain.max) issues.push(
|
|
2049
|
-
else if (base === Op.LT && value === domain.min) issues.push(
|
|
2227
|
+
if (base === Op.GTE && value === domain.min) issues.push(vacuousGte(isLength, groupIndex, constraintIndex, bigintToHex(value)));
|
|
2228
|
+
else if (base === Op.LTE && value === domain.max) issues.push(vacuousLte(isLength, groupIndex, constraintIndex, bigintToHex(value)));
|
|
2229
|
+
if (signedCompare(value, domain.min, domain.isSigned) < 0n || signedCompare(value, domain.max, domain.isSigned) > 0n) issues.push(outOfPhysicalBounds(isLength, groupIndex, constraintIndex, bigintToHex(value)));
|
|
2230
|
+
else if (base === Op.GT && value === domain.max) issues.push(impossibleGt(isLength, groupIndex, constraintIndex, bigintToHex(value)));
|
|
2231
|
+
else if (base === Op.LT && value === domain.min) issues.push(impossibleLt(isLength, groupIndex, constraintIndex, bigintToHex(value)));
|
|
2050
2232
|
if (base === Op.EQ) {
|
|
2051
2233
|
if (!domain.hasEq || domain.eq !== value) changedEq = true;
|
|
2052
2234
|
if (domain.hasEq) {
|
|
2053
|
-
if (domain.eq !== value) issues.push(
|
|
2235
|
+
if (domain.eq !== value) issues.push(conflictingEquality(isLength, groupIndex, constraintIndex, bigintToHex(domain.eq), bigintToHex(value)));
|
|
2054
2236
|
}
|
|
2055
|
-
for (let j = 0; j < domain.holes.length; j++) if (domain.holes[j] === value) issues.push(
|
|
2237
|
+
for (let j = 0; j < domain.holes.length; j++) if (domain.holes[j] === value) issues.push(eqNeqContradiction(isLength, groupIndex, constraintIndex, bigintToHex(value)));
|
|
2056
2238
|
domain.hasEq = true;
|
|
2057
2239
|
domain.eq = value;
|
|
2058
2240
|
} else if (base === Op.GT || base === Op.GTE) {
|
|
@@ -2060,15 +2242,15 @@ function updateBound(domain, base, isNegated, value, emit, groupIndex, constrain
|
|
|
2060
2242
|
if (domain.hasLower) {
|
|
2061
2243
|
let redundant = false;
|
|
2062
2244
|
let strictlyBetter = false;
|
|
2063
|
-
if (
|
|
2245
|
+
if (signedCompare(value, domain.lower, domain.isSigned) < 0n) redundant = true;
|
|
2064
2246
|
else if (value === domain.lower) {
|
|
2065
2247
|
if (domain.lowerInclusive) if (!inclusive) strictlyBetter = true;
|
|
2066
2248
|
else redundant = true;
|
|
2067
2249
|
else if (!inclusive) redundant = true;
|
|
2068
2250
|
} else strictlyBetter = true;
|
|
2069
|
-
if (redundant) issues.push(
|
|
2251
|
+
if (redundant) issues.push(dominatedBound(isLength, groupIndex, constraintIndex, bigintToHex(value)));
|
|
2070
2252
|
if (strictlyBetter) {
|
|
2071
|
-
issues.push(
|
|
2253
|
+
issues.push(dominatedBound(isLength, groupIndex, constraintIndex, bigintToHex(domain.lower)));
|
|
2072
2254
|
domain.lower = value;
|
|
2073
2255
|
domain.lowerInclusive = inclusive;
|
|
2074
2256
|
changedLower = true;
|
|
@@ -2084,15 +2266,15 @@ function updateBound(domain, base, isNegated, value, emit, groupIndex, constrain
|
|
|
2084
2266
|
if (domain.hasUpper) {
|
|
2085
2267
|
let redundant = false;
|
|
2086
2268
|
let strictlyBetter = false;
|
|
2087
|
-
if (
|
|
2269
|
+
if (signedCompare(value, domain.upper, domain.isSigned) > 0n) redundant = true;
|
|
2088
2270
|
else if (value === domain.upper) {
|
|
2089
2271
|
if (domain.upperInclusive) if (!inclusive) strictlyBetter = true;
|
|
2090
2272
|
else redundant = true;
|
|
2091
2273
|
else if (!inclusive) redundant = true;
|
|
2092
2274
|
} else strictlyBetter = true;
|
|
2093
|
-
if (redundant) issues.push(
|
|
2275
|
+
if (redundant) issues.push(dominatedBound(isLength, groupIndex, constraintIndex, bigintToHex(value)));
|
|
2094
2276
|
if (strictlyBetter) {
|
|
2095
|
-
issues.push(
|
|
2277
|
+
issues.push(dominatedBound(isLength, groupIndex, constraintIndex, bigintToHex(domain.upper)));
|
|
2096
2278
|
domain.upper = value;
|
|
2097
2279
|
domain.upperInclusive = inclusive;
|
|
2098
2280
|
changedUpper = true;
|
|
@@ -2105,16 +2287,16 @@ function updateBound(domain, base, isNegated, value, emit, groupIndex, constrain
|
|
|
2105
2287
|
}
|
|
2106
2288
|
}
|
|
2107
2289
|
if (changedEq || changedLower) {
|
|
2108
|
-
if (domain.hasEq && domain.hasLower) if (domain.lowerInclusive ?
|
|
2109
|
-
else issues.push(
|
|
2290
|
+
if (domain.hasEq && domain.hasLower) if (domain.lowerInclusive ? signedCompare(domain.eq, domain.lower, domain.isSigned) < 0n : signedCompare(domain.eq, domain.lower, domain.isSigned) <= 0n) issues.push(boundsExcludeEquality(isLength, groupIndex, constraintIndex, bigintToHex(domain.eq), bigintToHex(domain.lower)));
|
|
2291
|
+
else issues.push(redundantBound(isLength, groupIndex, constraintIndex, bigintToHex(domain.lower), bigintToHex(domain.eq)));
|
|
2110
2292
|
}
|
|
2111
2293
|
if (changedEq || changedUpper) {
|
|
2112
|
-
if (domain.hasEq && domain.hasUpper) if (domain.upperInclusive ?
|
|
2113
|
-
else issues.push(
|
|
2294
|
+
if (domain.hasEq && domain.hasUpper) if (domain.upperInclusive ? signedCompare(domain.eq, domain.upper, domain.isSigned) > 0n : signedCompare(domain.eq, domain.upper, domain.isSigned) >= 0n) issues.push(boundsExcludeEquality(isLength, groupIndex, constraintIndex, bigintToHex(domain.eq), bigintToHex(domain.upper)));
|
|
2295
|
+
else issues.push(redundantBound(isLength, groupIndex, constraintIndex, bigintToHex(domain.upper), bigintToHex(domain.eq)));
|
|
2114
2296
|
}
|
|
2115
2297
|
if (changedLower || changedUpper) {
|
|
2116
2298
|
if (domain.hasLower && domain.hasUpper) {
|
|
2117
|
-
if (
|
|
2299
|
+
if (signedCompare(domain.lower, domain.upper, domain.isSigned) > 0n || domain.lower === domain.upper && (!domain.lowerInclusive || !domain.upperInclusive)) issues.push(impossibleRange(isLength, groupIndex, constraintIndex, bigintToHex(domain.lower), bigintToHex(domain.upper)));
|
|
2118
2300
|
}
|
|
2119
2301
|
}
|
|
2120
2302
|
}
|
|
@@ -2210,6 +2392,40 @@ function checkDuplicates(issues, operators, groupIndex, constraintIndex) {
|
|
|
2210
2392
|
seen.add(key);
|
|
2211
2393
|
}
|
|
2212
2394
|
}
|
|
2395
|
+
/**
|
|
2396
|
+
* Report a lone unnegated gte/lte pair (value or length domain) as fusible into the
|
|
2397
|
+
* corresponding single range operator. Fires only for a satisfiable pair on a compatible
|
|
2398
|
+
* type; contradictions and dominated bounds are already reported by the domain checks.
|
|
2399
|
+
*/
|
|
2400
|
+
function checkFusibleRange(ctx, operators, groupIndex, constraintIndex, issues) {
|
|
2401
|
+
const numericPair = findLonePair(operators, Op.GTE, Op.LTE);
|
|
2402
|
+
if (numericPair && getIncompat(Op.GTE, ctx.typeInfo) === null && signedCompare(numericPair.low, numericPair.high, ctx.numeric.isSigned) <= 0n) issues.push(fusibleRange(false, groupIndex, constraintIndex, bigintToHex(numericPair.low), bigintToHex(numericPair.high)));
|
|
2403
|
+
const lengthPair = findLonePair(operators, Op.LENGTH_GTE, Op.LENGTH_LTE);
|
|
2404
|
+
if (lengthPair && getIncompat(Op.LENGTH_GTE, ctx.typeInfo) === null && lengthPair.low <= lengthPair.high) issues.push(fusibleRange(true, groupIndex, constraintIndex, bigintToHex(lengthPair.low), bigintToHex(lengthPair.high)));
|
|
2405
|
+
}
|
|
2406
|
+
/** Return the operand pair when the operators contain exactly one of each unnegated bound opcode. */
|
|
2407
|
+
function findLonePair(operators, lowerOp, upperOp) {
|
|
2408
|
+
let lowerCount = 0;
|
|
2409
|
+
let upperCount = 0;
|
|
2410
|
+
let low = 0n;
|
|
2411
|
+
let high = 0n;
|
|
2412
|
+
for (const opHex of operators) {
|
|
2413
|
+
const opCode = parseInt(opHex.slice(2, 4), 16);
|
|
2414
|
+
const dataLength = (opHex.length - 4) / 2;
|
|
2415
|
+
if (dataLength > 65535 || !isValidOperatorData(opCode & ~Op.NOT, dataLength)) continue;
|
|
2416
|
+
if (opCode === lowerOp) {
|
|
2417
|
+
lowerCount++;
|
|
2418
|
+
low = readValue(opHex);
|
|
2419
|
+
} else if (opCode === upperOp) {
|
|
2420
|
+
upperCount++;
|
|
2421
|
+
high = readValue(opHex);
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2424
|
+
return lowerCount === 1 && upperCount === 1 ? {
|
|
2425
|
+
low,
|
|
2426
|
+
high
|
|
2427
|
+
} : null;
|
|
2428
|
+
}
|
|
2213
2429
|
/** Validate all operators on a single constraint against its domain context. */
|
|
2214
2430
|
function validateConstraint(ctx, constraint, groupIndex, constraintIndex, issues) {
|
|
2215
2431
|
const operators = constraint.operators;
|
|
@@ -2218,21 +2434,36 @@ function validateConstraint(ctx, constraint, groupIndex, constraintIndex, issues
|
|
|
2218
2434
|
const opCode = parseInt(opHex.slice(2, 4), 16);
|
|
2219
2435
|
const base = opCode & ~Op.NOT;
|
|
2220
2436
|
const isNegated = (opCode & Op.NOT) !== 0;
|
|
2437
|
+
const dataLength = (opHex.length - 4) / 2;
|
|
2438
|
+
if (dataLength > 65535 || !isValidOperatorData(base, dataLength)) {
|
|
2439
|
+
issues.push(fromOpRule("UNKNOWN_OPERATOR", "Unknown operator code", groupIndex, constraintIndex, bigintToHex(BigInt(opCode))));
|
|
2440
|
+
continue;
|
|
2441
|
+
}
|
|
2221
2442
|
if (underAny && isNegated) issues.push(negationUnderAny(groupIndex, constraintIndex, bigintToHex(BigInt(opCode))));
|
|
2222
|
-
const
|
|
2223
|
-
if (
|
|
2224
|
-
issues.push(fromOpRule(
|
|
2443
|
+
const incompat = getIncompat(base, ctx.typeInfo);
|
|
2444
|
+
if (incompat) {
|
|
2445
|
+
issues.push(fromOpRule(incompat.code, incompat.message, groupIndex, constraintIndex, bigintToHex(BigInt(opCode))));
|
|
2225
2446
|
continue;
|
|
2226
2447
|
}
|
|
2227
2448
|
if (isValueOp(base)) {
|
|
2449
|
+
const typeCode = ctx.typeInfo.typeCode;
|
|
2450
|
+
if (isLeftAligned(typeCode)) {
|
|
2451
|
+
const nonCanonical = findNonCanonicalWord(opHex, typeCode);
|
|
2452
|
+
if (nonCanonical !== null) {
|
|
2453
|
+
issues.push(nonCanonicalOperand(groupIndex, constraintIndex, bigintToHex(nonCanonical.word), bigintToHex(nonCanonical.canonical)));
|
|
2454
|
+
continue;
|
|
2455
|
+
}
|
|
2456
|
+
}
|
|
2228
2457
|
if (base >= Op.EQ && base <= Op.BETWEEN) if (base === Op.BETWEEN) {
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2458
|
+
if (!isNegated) {
|
|
2459
|
+
const { low, high } = readPair(opHex);
|
|
2460
|
+
updateBound(ctx.numeric, Op.GTE, false, low, false, groupIndex, constraintIndex, issues);
|
|
2461
|
+
updateBound(ctx.numeric, Op.LTE, false, high, false, groupIndex, constraintIndex, issues);
|
|
2462
|
+
}
|
|
2232
2463
|
} else {
|
|
2233
2464
|
const value = readValue(opHex);
|
|
2234
2465
|
const holesBefore = ctx.numeric.holes.length;
|
|
2235
|
-
updateBound(ctx.numeric, base, isNegated, value,
|
|
2466
|
+
updateBound(ctx.numeric, base, isNegated, value, false, groupIndex, constraintIndex, issues);
|
|
2236
2467
|
if (ctx.numeric.holes.length > holesBefore) checkSetEmpty(ctx, groupIndex, constraintIndex, issues);
|
|
2237
2468
|
}
|
|
2238
2469
|
else if (isBitmaskOp(base)) updateBitmask(ctx, base, isNegated, readValue(opHex), groupIndex, constraintIndex, issues);
|
|
@@ -2242,12 +2473,14 @@ function validateConstraint(ctx, constraint, groupIndex, constraintIndex, issues
|
|
|
2242
2473
|
else updateSet(ctx, isNegated, values, groupIndex, constraintIndex, issues);
|
|
2243
2474
|
}
|
|
2244
2475
|
} else if (isLengthOp(base)) if (base === Op.LENGTH_BETWEEN) {
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2476
|
+
if (!isNegated) {
|
|
2477
|
+
const { low, high } = readPair(opHex);
|
|
2478
|
+
updateBound(ctx.length, Op.GTE, false, low, true, groupIndex, constraintIndex, issues);
|
|
2479
|
+
updateBound(ctx.length, Op.LTE, false, high, true, groupIndex, constraintIndex, issues);
|
|
2480
|
+
}
|
|
2248
2481
|
} else {
|
|
2249
2482
|
const value = readValue(opHex);
|
|
2250
|
-
updateBound(ctx.length, normalizeLengthOp(base), isNegated, value,
|
|
2483
|
+
updateBound(ctx.length, normalizeLengthOp(base), isNegated, value, true, groupIndex, constraintIndex, issues);
|
|
2251
2484
|
}
|
|
2252
2485
|
}
|
|
2253
2486
|
checkDuplicates(issues, operators, groupIndex, constraintIndex);
|
|
@@ -2291,6 +2524,7 @@ function validateGroup(data, descBytes, groupIndex, issues) {
|
|
|
2291
2524
|
contexts.push(ctx);
|
|
2292
2525
|
}
|
|
2293
2526
|
validateConstraint(ctx, constraint, groupIndex, constraintIndex, issues);
|
|
2527
|
+
checkFusibleRange(ctx, constraint.operators, groupIndex, constraintIndex, issues);
|
|
2294
2528
|
}
|
|
2295
2529
|
}
|
|
2296
2530
|
/**
|
|
@@ -2508,6 +2742,12 @@ var PolicyBuilder = class PolicyBuilder {
|
|
|
2508
2742
|
};
|
|
2509
2743
|
//#endregion
|
|
2510
2744
|
//#region src/constraint.ts
|
|
2745
|
+
/** Strip an optional 0x prefix and validate a 40-hex-char (20-byte) address body. */
|
|
2746
|
+
function addressBody(value) {
|
|
2747
|
+
const body = value.startsWith("0x") ? value.slice(2) : value;
|
|
2748
|
+
if (body.length !== 40) throw new CallciumError("INVALID_CONSTRAINT", `Invalid address length: expected 40 hex chars, got ${body.length}`);
|
|
2749
|
+
return body;
|
|
2750
|
+
}
|
|
2511
2751
|
/** Convert a scalar value to a 32-byte big-endian word. */
|
|
2512
2752
|
function encodeWord(value) {
|
|
2513
2753
|
const word = new Uint8Array(32);
|
|
@@ -2516,8 +2756,7 @@ function encodeWord(value) {
|
|
|
2516
2756
|
return word;
|
|
2517
2757
|
}
|
|
2518
2758
|
if (typeof value === "string") {
|
|
2519
|
-
const body =
|
|
2520
|
-
if (body.length !== 40) throw new CallciumError("INVALID_CONSTRAINT", `Invalid address length: expected 40 hex chars, got ${body.length}`);
|
|
2759
|
+
const body = addressBody(value);
|
|
2521
2760
|
for (let i = 0; i < 20; i++) {
|
|
2522
2761
|
const byte = parseInt(body.substring(i * 2, i * 2 + 2), 16);
|
|
2523
2762
|
if (Number.isNaN(byte)) throw new CallciumError("INVALID_CONSTRAINT", `Invalid hex character in address at position ${i * 2}`);
|
|
@@ -2554,8 +2793,7 @@ function setOp(opCode, values) {
|
|
|
2554
2793
|
if (typeof v === "bigint") return v;
|
|
2555
2794
|
if (typeof v === "number") return BigInt(v);
|
|
2556
2795
|
if (typeof v === "boolean") return v ? 1n : 0n;
|
|
2557
|
-
|
|
2558
|
-
return BigInt("0x" + (body || "0"));
|
|
2796
|
+
return BigInt("0x" + addressBody(v));
|
|
2559
2797
|
});
|
|
2560
2798
|
bigs.sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
2561
2799
|
const deduped = [];
|
|
@@ -2754,7 +2992,7 @@ function descendTuple(node, callData, head, base, childIndex) {
|
|
|
2754
2992
|
if (node.isDynamic) {
|
|
2755
2993
|
const ptrResult = readPointer(callData, head);
|
|
2756
2994
|
if (typeof ptrResult !== "number") return {
|
|
2757
|
-
|
|
2995
|
+
ok: false,
|
|
2758
2996
|
code: ptrResult.code
|
|
2759
2997
|
};
|
|
2760
2998
|
tupleBase = base + ptrResult;
|
|
@@ -2765,42 +3003,48 @@ function descendTuple(node, callData, head, base, childIndex) {
|
|
|
2765
3003
|
}
|
|
2766
3004
|
for (let i = 0; i < childIndex; i++) cursor += headContribution(node.fields[i]);
|
|
2767
3005
|
return {
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
3006
|
+
ok: true,
|
|
3007
|
+
location: {
|
|
3008
|
+
head: cursor,
|
|
3009
|
+
base: tupleBase,
|
|
3010
|
+
node: node.fields[childIndex]
|
|
3011
|
+
}
|
|
2772
3012
|
};
|
|
2773
3013
|
}
|
|
2774
3014
|
/** Descend into a dynamic array element by index. */
|
|
2775
3015
|
function descendDynamicArray(node, callData, head, base, childIndex) {
|
|
2776
3016
|
const arrayBaseResult = readPointer(callData, head);
|
|
2777
3017
|
if (typeof arrayBaseResult !== "number") return {
|
|
2778
|
-
|
|
3018
|
+
ok: false,
|
|
2779
3019
|
code: arrayBaseResult.code
|
|
2780
3020
|
};
|
|
2781
3021
|
const arrayBase = base + arrayBaseResult;
|
|
2782
3022
|
const lengthResult = readPointer(callData, arrayBase);
|
|
2783
3023
|
if (typeof lengthResult !== "number") return {
|
|
2784
|
-
|
|
3024
|
+
ok: false,
|
|
2785
3025
|
code: lengthResult.code
|
|
2786
3026
|
};
|
|
2787
3027
|
if (childIndex >= lengthResult) return {
|
|
2788
|
-
|
|
3028
|
+
ok: false,
|
|
2789
3029
|
code: "ARRAY_INDEX_OUT_OF_BOUNDS"
|
|
2790
3030
|
};
|
|
2791
3031
|
const headsBase = arrayBase + 32;
|
|
2792
3032
|
const elem = node.element;
|
|
2793
3033
|
if (elem.isDynamic) return {
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
3034
|
+
ok: true,
|
|
3035
|
+
location: {
|
|
3036
|
+
head: headsBase + childIndex * 32,
|
|
3037
|
+
base: headsBase,
|
|
3038
|
+
node: elem
|
|
3039
|
+
}
|
|
2798
3040
|
};
|
|
2799
3041
|
return {
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
3042
|
+
ok: true,
|
|
3043
|
+
location: {
|
|
3044
|
+
head: headsBase + childIndex * elem.staticSize,
|
|
3045
|
+
base: arrayBase,
|
|
3046
|
+
node: elem
|
|
3047
|
+
}
|
|
2804
3048
|
};
|
|
2805
3049
|
}
|
|
2806
3050
|
/** Descend into a static array element by index. */
|
|
@@ -2810,22 +3054,26 @@ function descendStaticArray(node, callData, head, base, childIndex) {
|
|
|
2810
3054
|
if (elem.isDynamic) {
|
|
2811
3055
|
const ptrResult = readPointer(callData, head);
|
|
2812
3056
|
if (typeof ptrResult !== "number") return {
|
|
2813
|
-
|
|
3057
|
+
ok: false,
|
|
2814
3058
|
code: ptrResult.code
|
|
2815
3059
|
};
|
|
2816
3060
|
const arrayBase = base + ptrResult;
|
|
2817
3061
|
return {
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
3062
|
+
ok: true,
|
|
3063
|
+
location: {
|
|
3064
|
+
head: arrayBase + childIndex * 32,
|
|
3065
|
+
base: arrayBase,
|
|
3066
|
+
node: elem
|
|
3067
|
+
}
|
|
2822
3068
|
};
|
|
2823
3069
|
}
|
|
2824
3070
|
return {
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
3071
|
+
ok: true,
|
|
3072
|
+
location: {
|
|
3073
|
+
head: head + childIndex * elem.staticSize,
|
|
3074
|
+
base,
|
|
3075
|
+
node: elem
|
|
3076
|
+
}
|
|
2829
3077
|
};
|
|
2830
3078
|
}
|
|
2831
3079
|
/**
|
|
@@ -2865,13 +3113,13 @@ function locate(tree, callData, pathBytes, baseOffset) {
|
|
|
2865
3113
|
};
|
|
2866
3114
|
}
|
|
2867
3115
|
const result = descend(node, callData, head, base, step);
|
|
2868
|
-
if (result.
|
|
3116
|
+
if (!result.ok) return {
|
|
2869
3117
|
ok: false,
|
|
2870
3118
|
code: result.code
|
|
2871
3119
|
};
|
|
2872
|
-
head = result.head;
|
|
2873
|
-
base = result.base;
|
|
2874
|
-
node = result.node;
|
|
3120
|
+
head = result.location.head;
|
|
3121
|
+
base = result.location.base;
|
|
3122
|
+
node = result.location.node;
|
|
2875
3123
|
}
|
|
2876
3124
|
return {
|
|
2877
3125
|
ok: true,
|
|
@@ -2883,6 +3131,29 @@ function locate(tree, callData, pathBytes, baseOffset) {
|
|
|
2883
3131
|
}
|
|
2884
3132
|
};
|
|
2885
3133
|
}
|
|
3134
|
+
/**
|
|
3135
|
+
* Build the element-shape fields shared by dynamic- and static-array nodes, branching only on
|
|
3136
|
+
* whether the element itself is dynamic.
|
|
3137
|
+
*/
|
|
3138
|
+
function makeArrayShape(length, elem, dynamicHeadsBase, staticDataOffset, staticCompositeBase) {
|
|
3139
|
+
return elem.isDynamic ? {
|
|
3140
|
+
length,
|
|
3141
|
+
elementNode: elem,
|
|
3142
|
+
elementIsDynamic: true,
|
|
3143
|
+
elementStaticSize: 0,
|
|
3144
|
+
headsBase: dynamicHeadsBase,
|
|
3145
|
+
dataOffset: 0,
|
|
3146
|
+
compositeBase: dynamicHeadsBase
|
|
3147
|
+
} : {
|
|
3148
|
+
length,
|
|
3149
|
+
elementNode: elem,
|
|
3150
|
+
elementIsDynamic: false,
|
|
3151
|
+
elementStaticSize: elem.staticSize,
|
|
3152
|
+
headsBase: 0,
|
|
3153
|
+
dataOffset: staticDataOffset,
|
|
3154
|
+
compositeBase: staticCompositeBase
|
|
3155
|
+
};
|
|
3156
|
+
}
|
|
2886
3157
|
/** Extract array shape from a location pointing to an array node. */
|
|
2887
3158
|
function arrayShape(callData, location) {
|
|
2888
3159
|
const { head, base, node } = location;
|
|
@@ -2898,66 +3169,26 @@ function arrayShape(callData, location) {
|
|
|
2898
3169
|
ok: false,
|
|
2899
3170
|
code: lengthResult.code
|
|
2900
3171
|
};
|
|
2901
|
-
const elem = node.element;
|
|
2902
3172
|
const headsBase = arrayBase + 32;
|
|
2903
|
-
if (elem.isDynamic) return {
|
|
2904
|
-
ok: true,
|
|
2905
|
-
shape: {
|
|
2906
|
-
length: lengthResult,
|
|
2907
|
-
elementNode: elem,
|
|
2908
|
-
elementIsDynamic: true,
|
|
2909
|
-
elementStaticSize: 0,
|
|
2910
|
-
headsBase,
|
|
2911
|
-
dataOffset: 0,
|
|
2912
|
-
compositeBase: headsBase
|
|
2913
|
-
}
|
|
2914
|
-
};
|
|
2915
3173
|
return {
|
|
2916
3174
|
ok: true,
|
|
2917
|
-
shape:
|
|
2918
|
-
length: lengthResult,
|
|
2919
|
-
elementNode: elem,
|
|
2920
|
-
elementIsDynamic: false,
|
|
2921
|
-
elementStaticSize: elem.staticSize,
|
|
2922
|
-
headsBase: 0,
|
|
2923
|
-
dataOffset: headsBase,
|
|
2924
|
-
compositeBase: arrayBase
|
|
2925
|
-
}
|
|
3175
|
+
shape: makeArrayShape(lengthResult, node.element, headsBase, headsBase, arrayBase)
|
|
2926
3176
|
};
|
|
2927
3177
|
}
|
|
2928
3178
|
if (node.type === "staticArray") {
|
|
2929
3179
|
const elem = node.element;
|
|
3180
|
+
let dynamicHeadsBase = 0;
|
|
2930
3181
|
if (elem.isDynamic) {
|
|
2931
3182
|
const arrayBaseResult = readPointer(callData, head);
|
|
2932
3183
|
if (typeof arrayBaseResult !== "number") return {
|
|
2933
3184
|
ok: false,
|
|
2934
3185
|
code: arrayBaseResult.code
|
|
2935
3186
|
};
|
|
2936
|
-
|
|
2937
|
-
return {
|
|
2938
|
-
ok: true,
|
|
2939
|
-
shape: {
|
|
2940
|
-
length: node.length,
|
|
2941
|
-
elementNode: elem,
|
|
2942
|
-
elementIsDynamic: true,
|
|
2943
|
-
elementStaticSize: 0,
|
|
2944
|
-
headsBase: arrayBase,
|
|
2945
|
-
dataOffset: 0,
|
|
2946
|
-
compositeBase: arrayBase
|
|
2947
|
-
}
|
|
2948
|
-
};
|
|
3187
|
+
dynamicHeadsBase = base + arrayBaseResult;
|
|
2949
3188
|
}
|
|
2950
3189
|
return {
|
|
2951
3190
|
ok: true,
|
|
2952
|
-
shape:
|
|
2953
|
-
length: node.length,
|
|
2954
|
-
elementNode: elem,
|
|
2955
|
-
elementIsDynamic: false,
|
|
2956
|
-
elementStaticSize: elem.staticSize,
|
|
2957
|
-
headsBase: 0,
|
|
2958
|
-
dataOffset: head,
|
|
2959
|
-
compositeBase: base
|
|
2960
|
-
}
|
|
3191
|
+
shape: makeArrayShape(node.length, elem, dynamicHeadsBase, head, base)
|
|
2961
3192
|
};
|
|
2962
3193
|
}
|
|
2963
3194
|
throw new CallciumError("INVALID_PATH", `Cannot compute array shape for non-array node (${node.type}).`);
|
|
@@ -3035,13 +3266,13 @@ function descendPath(callData, location, pathBytes) {
|
|
|
3035
3266
|
for (let stepIndex = 0; stepIndex < stepCount; stepIndex++) {
|
|
3036
3267
|
const step = readStep(pathBytes, stepIndex);
|
|
3037
3268
|
const result = descend(node, callData, head, base, step);
|
|
3038
|
-
if (result.
|
|
3269
|
+
if (!result.ok) return {
|
|
3039
3270
|
ok: false,
|
|
3040
3271
|
code: result.code
|
|
3041
3272
|
};
|
|
3042
|
-
head = result.head;
|
|
3043
|
-
base = result.base;
|
|
3044
|
-
node = result.node;
|
|
3273
|
+
head = result.location.head;
|
|
3274
|
+
base = result.location.base;
|
|
3275
|
+
node = result.location.node;
|
|
3045
3276
|
}
|
|
3046
3277
|
return {
|
|
3047
3278
|
ok: true,
|
|
@@ -3227,6 +3458,7 @@ function applyLeafOperator(callDataBytes, location, opCode, operandData) {
|
|
|
3227
3458
|
resolvedValue: bigintToHex(BigInt(valueLength))
|
|
3228
3459
|
};
|
|
3229
3460
|
}
|
|
3461
|
+
if (node.isDynamic || node.type !== "elementary") throw new CallciumError("NOT_SCALAR", "Value operator on a target without a scalar word.");
|
|
3230
3462
|
const result = loadScalar(callDataBytes, location);
|
|
3231
3463
|
if (!result.ok) return { error: result.code };
|
|
3232
3464
|
const value = canonicalize(toBigInt(result.value, 0), node.typeCode);
|