@rootzero/contracts 1.21.0 → 1.23.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.
Files changed (54) hide show
  1. package/CHANGELOG.md +125 -0
  2. package/Endpoints.sol +2 -3
  3. package/Events.sol +1 -1
  4. package/README.md +25 -19
  5. package/annotations/Action.sol +1 -1
  6. package/annotations/Label.sol +1 -1
  7. package/annotations/Schema.sol +36 -3
  8. package/codec/Blocks.sol +100 -29
  9. package/codec/Decoders.sol +62 -8
  10. package/codec/Readers.sol +39 -0
  11. package/codec/Schema.sol +44 -35
  12. package/codec/Writers.sol +8 -0
  13. package/commands/Allocate.sol +5 -8
  14. package/commands/Base.sol +29 -13
  15. package/commands/Burn.sol +5 -7
  16. package/commands/Credit.sol +5 -7
  17. package/commands/Debit.sol +5 -7
  18. package/commands/Deposit.sol +10 -14
  19. package/commands/Payout.sol +5 -8
  20. package/commands/Provision.sol +10 -14
  21. package/commands/Recover.sol +4 -6
  22. package/commands/Relay.sol +16 -22
  23. package/commands/Repay.sol +10 -14
  24. package/commands/Settle.sol +10 -14
  25. package/commands/Withdraw.sol +5 -7
  26. package/commands/admin/AllowAssets.sol +5 -7
  27. package/commands/admin/Allowance.sol +5 -7
  28. package/commands/admin/Annotate.sol +5 -7
  29. package/commands/admin/Appoint.sol +5 -7
  30. package/commands/admin/Authorize.sol +5 -7
  31. package/commands/admin/Base.sol +12 -3
  32. package/commands/admin/DenyAssets.sol +5 -7
  33. package/commands/admin/Dismiss.sol +5 -7
  34. package/commands/admin/Execute.sol +5 -7
  35. package/commands/admin/Unauthorize.sol +5 -7
  36. package/core/Calls.sol +72 -3
  37. package/core/Host.sol +7 -10
  38. package/core/Portal.sol +2 -2
  39. package/events/Dispatch.sol +1 -1
  40. package/events/Positioned.sol +22 -0
  41. package/events/Relay.sol +1 -1
  42. package/events/Route.sol +1 -1
  43. package/execution/Execution.sol +124 -4
  44. package/package.json +2 -3
  45. package/ports/Allowance.sol +22 -12
  46. package/ports/Assets.sol +99 -0
  47. package/ports/Dispatch.sol +2 -1
  48. package/utils/Cursors.sol +3 -3
  49. package/utils/Nodes.sol +1 -1
  50. package/utils/Utils.sol +31 -31
  51. package/docs/Schema.md +0 -427
  52. package/events/Commander.sol +0 -19
  53. package/ports/AllowAssets.sol +0 -34
  54. package/ports/DenyAssets.sol +0 -34
package/codec/Blocks.sol CHANGED
@@ -73,6 +73,17 @@ library Blocks {
73
73
  len = uint32(head >> 192);
74
74
  }
75
75
 
76
+ /// @notice Decode a complete block header within an absolute calldata region.
77
+ /// @param abs Absolute position of the header.
78
+ /// @param end Absolute region boundary.
79
+ /// @return key Decoded block key.
80
+ /// @return len Decoded payload length.
81
+ function peek(uint abs, uint end) internal pure returns (bytes4 key, uint len) {
82
+ if (abs > end || Sizes.Header > end - abs) revert MalformedBlocks();
83
+ (key, len) = header(abs);
84
+ if (len > end - abs - Sizes.Header) revert MalformedBlocks();
85
+ }
86
+
76
87
  /// @notice Validate a block header at an absolute calldata position.
77
88
  /// @dev DANGER: This performs an unchecked calldata read and does not ensure `end`
78
89
  /// lies within the caller's logical calldata region. Only the key, minimum,
@@ -101,6 +112,17 @@ library Blocks {
101
112
  end = body + size;
102
113
  }
103
114
 
115
+ /// @notice Validate an empty block at an absolute calldata position.
116
+ /// @dev DANGER: This performs an unchecked calldata read. The caller must
117
+ /// validate the returned end against its logical calldata region.
118
+ /// @param abs Absolute position of the block header.
119
+ /// @param key Expected block key.
120
+ /// @return end Absolute position immediately after the empty block header.
121
+ function expectEmpty(uint abs, bytes4 key) internal pure returns (uint end) {
122
+ if (header(abs, key) != 0) revert InvalidBlock();
123
+ return abs + Sizes.Header;
124
+ }
125
+
104
126
  /// @notice Return whether `abs` identifies a header with `key` before an absolute end.
105
127
  /// @param abs Absolute calldata position to inspect.
106
128
  /// @param end Absolute region boundary.
@@ -111,6 +133,16 @@ library Blocks {
111
133
  return bytes4(read32(abs)) == key;
112
134
  }
113
135
 
136
+ /// @notice Return whether `abs` identifies a complete empty block header.
137
+ /// @param abs Absolute position to inspect.
138
+ /// @param end Absolute region boundary.
139
+ /// @param key Expected block key.
140
+ /// @return Whether the expected key occurs with a zero-length payload.
141
+ function isEmpty(uint abs, uint end, bytes4 key) internal pure returns (bool) {
142
+ if (!hasAt(abs, end, key)) return false;
143
+ return uint32(uint(read32(abs)) >> 192) == 0;
144
+ }
145
+
114
146
  /// @notice Find the first block with `key` at or after absolute position `abs`.
115
147
  /// @param abs Absolute search position.
116
148
  /// @param end Absolute region boundary.
@@ -160,6 +192,18 @@ library Blocks {
160
192
 
161
193
  // Generic block writes
162
194
 
195
+ /// @notice Write an empty block header at `i`.
196
+ /// @dev DANGER: Unchecked memory write. Reserve `Sizes.Header` bytes first.
197
+ /// @param dst Destination buffer.
198
+ /// @param i Relative write position.
199
+ /// @param key Block key.
200
+ function writeEmpty(bytes memory dst, uint i, bytes4 key) internal pure {
201
+ uint head = uint(uint32(key)) << 224;
202
+ assembly ("memory-safe") {
203
+ mstore(add(add(dst, 0x20), i), head)
204
+ }
205
+ }
206
+
163
207
  /// @notice Write a custom block with one payload word at `i`.
164
208
  /// @dev DANGER: Unchecked memory write. Reserve `Sizes.B32` bytes first.
165
209
  /// @param dst Destination buffer.
@@ -1729,6 +1773,14 @@ library Blocks {
1729
1773
 
1730
1774
  // Generic factories
1731
1775
 
1776
+ /// @notice Encode an empty block.
1777
+ /// @param key Block type key.
1778
+ /// @return value Encoded empty block header.
1779
+ function createEmpty(bytes4 key) internal pure returns (bytes memory value) {
1780
+ value = allocate(Sizes.Header);
1781
+ writeEmpty(value, 0, key);
1782
+ }
1783
+
1732
1784
  /// @notice Encode a block with a raw payload.
1733
1785
  /// @param key Block type key.
1734
1786
  /// @param payload Raw payload bytes.
@@ -1749,28 +1801,28 @@ library Blocks {
1749
1801
  // Dynamic leaf factories
1750
1802
 
1751
1803
  /// @notice Encode a LIST block.
1752
- function list(bytes memory value) internal pure returns (bytes memory blockdata) {
1804
+ function createList(bytes memory value) internal pure returns (bytes memory blockdata) {
1753
1805
  uint len = max32(value.length);
1754
1806
  blockdata = allocate(Sizes.Header + len);
1755
1807
  writeList(blockdata, 0, value);
1756
1808
  }
1757
1809
 
1758
1810
  /// @notice Encode a LIST block by copying its payload from calldata.
1759
- function listCopy(bytes calldata value) internal pure returns (bytes memory blockdata) {
1811
+ function createListCopy(bytes calldata value) internal pure returns (bytes memory blockdata) {
1760
1812
  uint len = max32(value.length);
1761
1813
  blockdata = allocate(Sizes.Header + len);
1762
1814
  copyList(blockdata, 0, value);
1763
1815
  }
1764
1816
 
1765
1817
  /// @notice Encode an EVM block.
1766
- function evm(bytes memory value) internal pure returns (bytes memory blockdata) {
1818
+ function createEvm(bytes memory value) internal pure returns (bytes memory blockdata) {
1767
1819
  uint len = max32(value.length);
1768
1820
  blockdata = allocate(Sizes.Header + len);
1769
1821
  writeEvm(blockdata, 0, value);
1770
1822
  }
1771
1823
 
1772
1824
  /// @notice Encode an EVM block by copying its payload from calldata.
1773
- function evmCopy(bytes calldata value) internal pure returns (bytes memory blockdata) {
1825
+ function createEvmCopy(bytes calldata value) internal pure returns (bytes memory blockdata) {
1774
1826
  uint len = max32(value.length);
1775
1827
  blockdata = allocate(Sizes.Header + len);
1776
1828
  copyEvm(blockdata, 0, value);
@@ -1779,14 +1831,14 @@ library Blocks {
1779
1831
  /// @notice Encode a BYTES block with a raw payload.
1780
1832
  /// @param value Raw payload bytes.
1781
1833
  /// @return blockdata Encoded BYTES block bytes.
1782
- function data(bytes memory value) internal pure returns (bytes memory blockdata) {
1834
+ function createBytes(bytes memory value) internal pure returns (bytes memory blockdata) {
1783
1835
  uint len = max32(value.length);
1784
1836
  blockdata = allocate(Sizes.Header + len);
1785
1837
  writeBytes(blockdata, 0, value);
1786
1838
  }
1787
1839
 
1788
1840
  /// @notice Encode a BYTES block by copying its payload from calldata.
1789
- function dataCopy(bytes calldata value) internal pure returns (bytes memory blockdata) {
1841
+ function createBytesCopy(bytes calldata value) internal pure returns (bytes memory blockdata) {
1790
1842
  uint len = max32(value.length);
1791
1843
  blockdata = allocate(Sizes.Header + len);
1792
1844
  copyBytes(blockdata, 0, value);
@@ -1795,14 +1847,14 @@ library Blocks {
1795
1847
  /// @notice Encode a STRING block with a UTF-8 payload.
1796
1848
  /// @param value String payload.
1797
1849
  /// @return blockdata Encoded STRING block bytes.
1798
- function text(string memory value) internal pure returns (bytes memory blockdata) {
1850
+ function createString(string memory value) internal pure returns (bytes memory blockdata) {
1799
1851
  uint len = max32(bytes(value).length);
1800
1852
  blockdata = allocate(Sizes.Header + len);
1801
1853
  writeString(blockdata, 0, value);
1802
1854
  }
1803
1855
 
1804
1856
  /// @notice Encode a STRING block by copying its payload from calldata.
1805
- function textCopy(string calldata value) internal pure returns (bytes memory blockdata) {
1857
+ function createStringCopy(string calldata value) internal pure returns (bytes memory blockdata) {
1806
1858
  uint len = max32(bytes(value).length);
1807
1859
  blockdata = allocate(Sizes.Header + len);
1808
1860
  copyString(blockdata, 0, value);
@@ -1814,7 +1866,7 @@ library Blocks {
1814
1866
  /// @param namespace Label namespace.
1815
1867
  /// @param name Label text.
1816
1868
  /// @return value Encoded LABEL block bytes.
1817
- function label(bytes32 namespace, string memory name) internal pure returns (bytes memory value) {
1869
+ function createLabel(bytes32 namespace, string memory name) internal pure returns (bytes memory value) {
1818
1870
  uint len = max32(Sizes.B32 + bytes(name).length);
1819
1871
  value = allocate(Sizes.Header + len);
1820
1872
  writeLabel(value, 0, namespace, name);
@@ -1823,7 +1875,7 @@ library Blocks {
1823
1875
  /// @notice Encode an ACTION annotation block.
1824
1876
  /// @param actionid Canonical semantic action identifier.
1825
1877
  /// @return value Encoded ACTION block bytes.
1826
- function action(uint actionid) internal pure returns (bytes memory value) {
1878
+ function createAction(uint actionid) internal pure returns (bytes memory value) {
1827
1879
  value = allocate(Sizes.B32);
1828
1880
  write32(value, 0, Keys.Action, bytes32(actionid));
1829
1881
  }
@@ -1831,9 +1883,17 @@ library Blocks {
1831
1883
  /// @notice Encode a SCHEMA block.
1832
1884
  /// @param spec Block specification.
1833
1885
  /// @param body Schema body.
1886
+ /// @return value Encoded SCHEMA block bytes.
1887
+ function createSchema(uint spec, string memory body) internal pure returns (bytes memory value) {
1888
+ return createSchema(spec, body, bytes32(0));
1889
+ }
1890
+
1891
+ /// @notice Encode a named SCHEMA block.
1892
+ /// @param spec Block specification.
1893
+ /// @param body Schema body.
1834
1894
  /// @param name Schema name.
1835
1895
  /// @return value Encoded SCHEMA block bytes.
1836
- function schema(uint spec, string memory body, bytes32 name) internal pure returns (bytes memory value) {
1896
+ function createSchema(uint spec, string memory body, bytes32 name) internal pure returns (bytes memory value) {
1837
1897
  uint len = max32(Sizes.B64 + bytes(body).length);
1838
1898
  value = allocate(Sizes.Header + len);
1839
1899
  writeSchema(value, 0, spec, body, name);
@@ -1841,11 +1901,20 @@ library Blocks {
1841
1901
 
1842
1902
  // Fixed-width factories
1843
1903
 
1904
+ /// @notice Encode an AMOUNT block.
1905
+ /// @param asset Asset identifier.
1906
+ /// @param amount Token amount.
1907
+ /// @return value Encoded AMOUNT block bytes.
1908
+ function createAmount(bytes32 asset, uint amount) internal pure returns (bytes memory value) {
1909
+ value = allocate(Sizes.Amount);
1910
+ writeAmount(value, 0, asset, amount);
1911
+ }
1912
+
1844
1913
  /// @notice Encode a BALANCE block.
1845
1914
  /// @param asset Asset identifier.
1846
1915
  /// @param amount Token amount.
1847
1916
  /// @return value Encoded BALANCE block bytes.
1848
- function balance(bytes32 asset, uint amount) internal pure returns (bytes memory value) {
1917
+ function createBalance(bytes32 asset, uint amount) internal pure returns (bytes memory value) {
1849
1918
  value = allocate(Sizes.Balance);
1850
1919
  writeBalance(value, 0, asset, amount);
1851
1920
  }
@@ -1855,7 +1924,7 @@ library Blocks {
1855
1924
  /// @param asset Asset identifier.
1856
1925
  /// @param amount Token amount.
1857
1926
  /// @return value Encoded CUSTODY block bytes.
1858
- function custody(uint host, bytes32 asset, uint amount) internal pure returns (bytes memory value) {
1927
+ function createCustody(uint host, bytes32 asset, uint amount) internal pure returns (bytes memory value) {
1859
1928
  value = allocate(Sizes.B96);
1860
1929
  writeCustody(value, 0, host, asset, amount);
1861
1930
  }
@@ -1866,7 +1935,7 @@ library Blocks {
1866
1935
  /// @param liability Identifier for the liability side.
1867
1936
  /// @param debt Quantity owed on the liability side.
1868
1937
  /// @return value Encoded POSITION block bytes.
1869
- function position(
1938
+ function createPosition(
1870
1939
  bytes32 asset,
1871
1940
  uint amount,
1872
1941
  bytes32 liability,
@@ -1882,7 +1951,7 @@ library Blocks {
1882
1951
  /// @param asset Asset identifier.
1883
1952
  /// @param amount Transfer amount.
1884
1953
  /// @return value Encoded TRANSACTION block bytes.
1885
- function transaction(
1954
+ function createTransaction(
1886
1955
  bytes32 from,
1887
1956
  bytes32 to,
1888
1957
  bytes32 asset,
@@ -1899,14 +1968,14 @@ library Blocks {
1899
1968
  /// @param resources Packed resources assigned to the step.
1900
1969
  /// @param input Raw nested input payload.
1901
1970
  /// @return value Encoded STEP block bytes.
1902
- function step(uint cmd, uint resources, bytes memory input) internal pure returns (bytes memory value) {
1971
+ function createStep(uint cmd, uint resources, bytes memory input) internal pure returns (bytes memory value) {
1903
1972
  uint len = max32(Sizes.B64 + Sizes.Header + input.length);
1904
1973
  value = allocate(len);
1905
1974
  writeStep(value, 0, cmd, resources, input);
1906
1975
  }
1907
1976
 
1908
1977
  /// @notice Encode a STEP block by copying its nested input from calldata.
1909
- function stepCopy(uint cmd, uint resources, bytes calldata input) internal pure returns (bytes memory value) {
1978
+ function createStepCopy(uint cmd, uint resources, bytes calldata input) internal pure returns (bytes memory value) {
1910
1979
  uint len = max32(Sizes.B64 + Sizes.Header + input.length);
1911
1980
  value = allocate(len);
1912
1981
  copyStep(value, 0, cmd, resources, input);
@@ -1917,50 +1986,52 @@ library Blocks {
1917
1986
  /// @param resources Packed resources assigned to the call.
1918
1987
  /// @param payload Raw calldata payload for the target.
1919
1988
  /// @return value Encoded CALL block bytes.
1920
- function call(uint target, uint resources, bytes memory payload) internal pure returns (bytes memory value) {
1989
+ function createCall(uint target, uint resources, bytes memory payload) internal pure returns (bytes memory value) {
1921
1990
  uint len = max32(Sizes.B64 + Sizes.Header + payload.length);
1922
1991
  value = allocate(len);
1923
1992
  writeCall(value, 0, target, resources, payload);
1924
1993
  }
1925
1994
 
1926
1995
  /// @notice Encode a CALL block by copying its nested payload from calldata.
1927
- function callCopy(uint target, uint resources, bytes calldata payload) internal pure returns (bytes memory value) {
1996
+ function createCallCopy(uint target, uint resources, bytes calldata payload) internal pure returns (bytes memory value) {
1928
1997
  uint len = max32(Sizes.B64 + Sizes.Header + payload.length);
1929
1998
  value = allocate(len);
1930
1999
  copyCall(value, 0, target, resources, payload);
1931
2000
  }
1932
2001
 
1933
2002
  /// @notice Encode a RELAY block.
1934
- /// @param portal Destination portal identifier, often the destination host ID.
2003
+ /// @param portal Destination portal implementation's host ID, passed through
2004
+ /// without semantic validation.
1935
2005
  /// @param resources Chain-specific resources for the destination context.
1936
2006
  /// @param input Nested input block stream.
1937
2007
  /// @return value Encoded RELAY block bytes.
1938
- function relay(uint portal, uint resources, bytes memory input) internal pure returns (bytes memory value) {
2008
+ function createRelay(uint portal, uint resources, bytes memory input) internal pure returns (bytes memory value) {
1939
2009
  uint len = max32(Sizes.B64 + Sizes.Header + input.length);
1940
2010
  value = allocate(len);
1941
2011
  writeRelay(value, 0, portal, resources, input);
1942
2012
  }
1943
2013
 
1944
2014
  /// @notice Encode a RELAY block by copying its nested input from calldata.
1945
- function relayCopy(uint portal, uint resources, bytes calldata input) internal pure returns (bytes memory value) {
2015
+ function createRelayCopy(uint portal, uint resources, bytes calldata input) internal pure returns (bytes memory value) {
1946
2016
  uint len = max32(Sizes.B64 + Sizes.Header + input.length);
1947
2017
  value = allocate(len);
1948
2018
  copyRelay(value, 0, portal, resources, input);
1949
2019
  }
1950
2020
 
1951
2021
  /// @notice Encode a DISPATCH block.
1952
- /// @param portal Destination portal identifier, often the destination host ID.
2022
+ /// @param portal Destination portal implementation's host ID, passed through
2023
+ /// without semantic validation.
1953
2024
  /// @param resources Chain-specific resources for the destination dispatch.
1954
2025
  /// @param payload Encoded payload.
1955
2026
  /// @return value Encoded DISPATCH block bytes.
1956
- function dispatch(uint portal, uint resources, bytes memory payload) internal pure returns (bytes memory value) {
2027
+ function createDispatch(uint portal, uint resources, bytes memory payload) internal pure returns (bytes memory value) {
1957
2028
  uint len = max32(Sizes.B64 + Sizes.Header + payload.length);
1958
2029
  value = allocate(len);
1959
2030
  writeDispatch(value, 0, portal, resources, payload);
1960
2031
  }
1961
2032
 
1962
2033
  /// @notice Encode a DISPATCH block by copying its nested payload from calldata.
1963
- function dispatchCopy(
2034
+ function createDispatchCopy(
1964
2035
  uint portal,
1965
2036
  uint resources,
1966
2037
  bytes calldata payload
@@ -1971,7 +2042,7 @@ library Blocks {
1971
2042
  }
1972
2043
 
1973
2044
  /// @notice Encode a CONTEXT block.
1974
- function context(
2045
+ function createContext(
1975
2046
  bytes32 account,
1976
2047
  bytes memory state,
1977
2048
  bytes memory input
@@ -1982,7 +2053,7 @@ library Blocks {
1982
2053
  }
1983
2054
 
1984
2055
  /// @notice Encode a CONTEXT block by copying its nested streams from calldata.
1985
- function contextCopy(
2056
+ function createContextCopy(
1986
2057
  bytes32 account,
1987
2058
  bytes calldata state,
1988
2059
  bytes calldata input
@@ -1993,7 +2064,7 @@ library Blocks {
1993
2064
  }
1994
2065
 
1995
2066
  /// @notice Encode a RECOVER block.
1996
- function recover(
2067
+ function createRecover(
1997
2068
  uint handler,
1998
2069
  uint resources,
1999
2070
  bytes32 recoverykey,
@@ -2005,7 +2076,7 @@ library Blocks {
2005
2076
  }
2006
2077
 
2007
2078
  /// @notice Encode a RECOVER block by copying its nested witness from calldata.
2008
- function recoverCopy(
2079
+ function createRecoverCopy(
2009
2080
  uint handler,
2010
2081
  uint resources,
2011
2082
  bytes32 recoverykey,
@@ -52,6 +52,13 @@ library Decoders {
52
52
  return cur.state.more();
53
53
  }
54
54
 
55
+ /// @notice Return the cursor's current absolute calldata position.
56
+ /// @param cur Cursor to inspect.
57
+ /// @return Current absolute calldata position.
58
+ function absolute(Cur memory cur) internal pure returns (uint) {
59
+ return cur.state.absolute();
60
+ }
61
+
55
62
  /// @notice Validate and consume the next block from a cursor.
56
63
  /// @param cur Cursor advanced over the complete block.
57
64
  /// @param spec Expected block specification.
@@ -62,6 +69,18 @@ library Decoders {
62
69
  cur.state = cur.state.seekAbs(end);
63
70
  }
64
71
 
72
+ /// @notice Consume a matching empty block from a cursor when present.
73
+ /// @param cur Cursor advanced only when the matching block is empty.
74
+ /// @param key Expected block key.
75
+ /// @return Whether an empty block was consumed.
76
+ function tryConsumeEmpty(Cur memory cur, bytes4 key) internal pure returns (bool) {
77
+ (uint i, uint offset, uint size) = cur.state.decode();
78
+ (bytes4 current, uint len) = Blocks.peek(offset + i, offset + size);
79
+ if (current != key || len != 0) return false;
80
+ cur.state = cur.state.seek(i + Sizes.Header);
81
+ return true;
82
+ }
83
+
65
84
  /// @notice Validate and enter the payload of the next block in a cursor.
66
85
  /// @dev The cursor remains in its existing frame so callers can decode child
67
86
  /// blocks in place. Callers should prove complete payload consumption with
@@ -71,8 +90,38 @@ library Decoders {
71
90
  /// @return abs Absolute position of the first payload byte.
72
91
  /// @return end Absolute position immediately after the payload.
73
92
  function enter(Cur memory cur, uint spec) internal pure returns (uint abs, uint end) {
93
+ return enter(cur, spec, 0);
94
+ }
95
+
96
+ /// @notice Validate a parent block and advance over a fixed payload prefix.
97
+ /// @dev `amount` is relative to the payload start and cannot exceed the
98
+ /// current parent payload. The returned `abs` remains the payload start.
99
+ /// @param cur Cursor advanced over the block header and fixed prefix.
100
+ /// @param spec Expected parent block specification.
101
+ /// @param amount Number of initial payload bytes to advance over.
102
+ /// @return abs Absolute position of the first payload byte.
103
+ /// @return end Absolute position immediately after the payload.
104
+ function enter(Cur memory cur, uint spec, uint amount) internal pure returns (uint abs, uint end) {
74
105
  (abs, end) = Blocks.expect(cur.state.absolute(), spec);
75
- cur.state = cur.state.seekAbs(abs);
106
+ if (amount > end - abs) revert Blocks.InvalidBlock();
107
+ cur.state = cur.state.seekAbs(abs + amount);
108
+ }
109
+
110
+ /// @notice Advance a cursor by a raw byte count.
111
+ /// @dev No block header or schema is validated.
112
+ /// @param cur Cursor advanced by `amount` bytes.
113
+ /// @param amount Number of bytes to advance.
114
+ function advance(Cur memory cur, uint amount) internal pure {
115
+ cur.state = cur.state.advance(amount);
116
+ }
117
+
118
+ /// @notice Take a raw byte range from the cursor.
119
+ /// @dev No block header or schema is validated.
120
+ /// @param cur Cursor advanced by `amount` bytes.
121
+ /// @param amount Number of bytes to take.
122
+ /// @return abs Absolute position of the first taken byte.
123
+ function take(Cur memory cur, uint amount) internal pure returns (uint abs) {
124
+ (cur.state, abs) = cur.state.consume(amount);
76
125
  }
77
126
 
78
127
  /// @notice Require a decoder cursor to be at absolute position `abs`.
@@ -128,9 +177,7 @@ library Decoders {
128
177
  /// @return len Payload length.
129
178
  function peek(Cur memory cur, uint i) internal pure returns (bytes4 key, uint len) {
130
179
  (, uint offset, uint size) = cur.state.decode();
131
- if (i > size || Sizes.Header > size - i) revert Blocks.MalformedBlocks();
132
- (key, len) = Blocks.header(offset + i);
133
- if (len > size - i - Sizes.Header) revert Blocks.MalformedBlocks();
180
+ return Blocks.peek(offset + i, offset + size);
134
181
  }
135
182
 
136
183
  /// @notice Return the relative position immediately after the current block.
@@ -161,6 +208,15 @@ library Decoders {
161
208
  return Blocks.hasAt(offset + i, offset + len, key);
162
209
  }
163
210
 
211
+ /// @notice Return whether the current block has `key` and an empty payload.
212
+ /// @param cur Cursor positioned at a block.
213
+ /// @param key Expected block key.
214
+ /// @return Whether a complete matching empty block header occurs at the current position.
215
+ function isEmpty(Cur memory cur, bytes4 key) internal pure returns (bool) {
216
+ (uint i, uint offset, uint len) = cur.state.decode();
217
+ return Blocks.isEmpty(offset + i, offset + len, key);
218
+ }
219
+
164
220
  /// @notice Find `key` at or after relative position `i`.
165
221
  /// @param cur Cursor to search.
166
222
  /// @param i Relative search position.
@@ -210,7 +266,7 @@ library Decoders {
210
266
  /// @param cur Cursor advanced past the block.
211
267
  /// @param key Expected block key.
212
268
  /// @return out Cursor spanning the complete encoded block.
213
- function take(Cur memory cur, bytes4 key) internal pure returns (Cur memory out) {
269
+ function takeBlock(Cur memory cur, bytes4 key) internal pure returns (Cur memory out) {
214
270
  uint abs = cur.state.absolute();
215
271
  (, uint end) = consume(cur, Specs.create(key, 0, 0, 0));
216
272
  out.state = Cursors.create(abs, end - abs, 0, 0, 0);
@@ -256,9 +312,7 @@ library Decoders {
256
312
 
257
313
  /// @dev Return the next raw calldata word and advance by `size` bytes.
258
314
  function next(Cur memory cur, uint size) private pure returns (bytes32 value) {
259
- uint abs;
260
- (cur.state, abs) = cur.state.consume(size);
261
- value = Blocks.read32(abs);
315
+ value = Blocks.read32(take(cur, size));
262
316
  }
263
317
 
264
318
  /// @notice Return the next raw byte and advance the cursor by one byte.
package/codec/Readers.sol CHANGED
@@ -45,6 +45,45 @@ library Readers {
45
45
  return cur.i != cur.source.length;
46
46
  }
47
47
 
48
+ /// @notice Return whether the current block has `key` and an empty payload.
49
+ /// @param cur Reader inspected without advancing.
50
+ /// @param key Expected block key.
51
+ /// @return Whether a complete matching empty block header occurs at the current position.
52
+ function isEmpty(Reader memory cur, bytes4 key) internal pure returns (bool) {
53
+ bytes memory source = cur.source;
54
+ uint i = cur.i;
55
+ if (i > source.length || source.length - i < Sizes.Header) return false;
56
+
57
+ uint header;
58
+ assembly ("memory-safe") {
59
+ header := mload(add(add(source, 0x20), i))
60
+ }
61
+ return bytes4(uint32(header >> 224)) == key && uint32(header >> 192) == 0;
62
+ }
63
+
64
+ /// @notice Consume a matching empty block from the reader when present.
65
+ /// @param cur Reader advanced only when the matching block is empty.
66
+ /// @param key Expected block key.
67
+ /// @return Whether an empty block was consumed.
68
+ function tryConsumeEmpty(Reader memory cur, bytes4 key) internal pure returns (bool) {
69
+ bytes memory source = cur.source;
70
+ uint i = cur.i;
71
+ if (i > source.length || source.length - i < Sizes.Header) revert InvalidBlock();
72
+
73
+ bytes4 current;
74
+ uint len;
75
+ assembly ("memory-safe") {
76
+ let header := mload(add(add(source, 0x20), i))
77
+ current := header
78
+ len := and(shr(192, header), 0xffffffff)
79
+ }
80
+
81
+ if (len > source.length - i - Sizes.Header) revert InvalidBlock();
82
+ if (current != key || len != 0) return false;
83
+ cur.i += Sizes.Header;
84
+ return true;
85
+ }
86
+
48
87
  /// @notice Validate and consume the current block, advancing `cur.i` past it.
49
88
  /// @param cur Reader to advance.
50
89
  /// @param key Expected block key.
package/codec/Schema.sol CHANGED
@@ -8,24 +8,34 @@ pragma solidity ^0.8.33;
8
8
  //
9
9
  // Schema:
10
10
  // - block aliases are published separately from payload schemas
11
- // - payload schemas are `""`, `{ fields }`, or a top-level `many #x`
11
+ // - payload schemas are `""` or a comma-separated item sequence; one optional
12
+ // pair of outer braces may wrap a non-empty sequence without changing meaning
12
13
  // - an empty schema string means the block has no structured payload
13
14
  // - commas separate siblings at every level
14
- // - braces define the current block payload body
15
+ // - braces are presentation-only and do not change payload layout
15
16
  // - command inputs are a single run when the input schema is non-empty
16
17
  // - command state is a single active state run without trailing globals
17
18
  // - run items may repeat at top level for batching
18
- // - `maybe #x` marks an optional block item
19
- // - nested `many #x` emits one generic list block containing repeated `#x` items
20
- // - a custom schema consisting of top-level `many #x` uses its custom key for
21
- // the outer list block and contains repeated `#x` items directly
19
+ // - every declared child block header is present when its parent is non-empty
20
+ // - any block may use a zero-length payload as its empty form
21
+ // - `maybe #x` hints that the onchain consumer accepts the empty form of `#x`
22
+ // when emptiness is not already intrinsic to the referenced block type
23
+ // - `many #x` alongside other items emits one generic list block containing
24
+ // zero or more repeated `#x` items; the list header is always present
25
+ // - a custom schema consisting of exactly one `many #x` item uses its custom
26
+ // key for the outer list block and contains repeated `#x` items directly,
27
+ // whether or not the item is wrapped in braces
22
28
  // - endpoint descriptor lanes identify their top-level block key directly
23
- // - `portal` fields are routing identifiers, often destination host IDs
29
+ // - `portal` fields identify destination portal hosts. By convention the value
30
+ // is the portal implementation's host ID; core passes it through unchanged
31
+ // and hooks may validate or resolve it for their transport
24
32
  // - `resources` fields are chain-specific resource words. A portal adapter
25
33
  // interprets them for the destination runtime. EVM resources use the low
26
34
  // 128 bits as native value.
27
35
  // - dotted field names and aliases, e.g. `dst.portal` or `#bytes as dst.payload`,
28
36
  // are offchain projection metadata only and do not change runtime encoding
37
+ // - `at N` assigns an offchain presentation position to one sibling; explicit
38
+ // positions are reserved first and unannotated siblings retain relative order
29
39
  // - child blocks resolve by alias in the active schema context; unresolved aliases are invalid
30
40
  // - schema strings describe the payload body only; the `Block` event carries the alias
31
41
  // - items are encoded in declaration order
@@ -63,7 +73,6 @@ pragma solidity ^0.8.33;
63
73
  library Schemas {
64
74
  // Empty and reserved payloads
65
75
 
66
- string constant Unit = "";
67
76
  string constant Bytes = "";
68
77
  string constant String = "";
69
78
  string constant List = "";
@@ -71,47 +80,47 @@ library Schemas {
71
80
 
72
81
  // One-word payloads
73
82
 
74
- string constant Node = "{ uint id }";
75
- string constant Account = "{ bytes32 account }";
76
- string constant Asset = "{ bytes32 asset }";
77
- string constant Status = "{ uint code }";
83
+ string constant Node = "uint node";
84
+ string constant Account = "bytes32 account";
85
+ string constant Asset = "bytes32 asset";
86
+ string constant Status = "uint code";
78
87
 
79
88
  // Two-word payloads
80
89
 
81
- string constant Amount = "{ bytes32 asset, uint amount }";
82
- string constant Balance = "{ bytes32 asset, uint amount }";
83
- string constant AccountAsset = "{ bytes32 account, bytes32 asset }";
84
- string constant HostAsset = "{ uint host, bytes32 asset }";
90
+ string constant Amount = "bytes32 asset, uint amount";
91
+ string constant Balance = "bytes32 asset, uint amount";
92
+ string constant AccountAsset = "bytes32 account, bytes32 asset";
93
+ string constant HostAsset = "uint host, bytes32 asset";
85
94
 
86
95
  // Three-word payloads
87
96
 
88
- string constant Allocation = "{ uint host, bytes32 asset, uint amount }";
89
- string constant Allowance = "{ uint host, bytes32 asset, uint amount }";
90
- string constant Custody = "{ uint host, bytes32 asset, uint amount }";
91
- string constant AccountAmount = "{ bytes32 account, bytes32 asset, uint amount }";
92
- string constant HostAmount = "{ uint host, bytes32 asset, uint amount }";
93
- string constant HostAccountAsset = "{ uint host, bytes32 account, bytes32 asset }";
97
+ string constant Allocation = "uint host, bytes32 asset, uint amount";
98
+ string constant Allowance = "uint host, bytes32 asset, uint amount";
99
+ string constant Custody = "uint host, bytes32 asset, uint amount";
100
+ string constant AccountAmount = "bytes32 account, bytes32 asset, uint amount";
101
+ string constant HostAmount = "uint host, bytes32 asset, uint amount";
102
+ string constant HostAccountAsset = "uint host, bytes32 account, bytes32 asset";
94
103
 
95
104
  // Four-word payloads
96
105
 
97
- string constant Position = "{ bytes32 asset, uint amount, bytes32 liability, uint debt }";
98
- string constant Transaction = "{ bytes32 from, bytes32 to, bytes32 asset, uint amount }";
99
- string constant HostAccountAmount = "{ uint host, bytes32 account, bytes32 asset, uint amount }";
106
+ string constant Position = "bytes32 asset, uint amount, bytes32 liability, uint debt";
107
+ string constant Transaction = "bytes32 from, bytes32 to, bytes32 asset, uint amount";
108
+ string constant HostAccountAmount = "uint host, bytes32 account, bytes32 asset, uint amount";
100
109
 
101
110
  // Composite payloads
102
111
 
103
- string constant Call = "{ uint target, uint resources, #bytes as payload }";
104
- string constant Step = "{ uint cmd, uint resources, #bytes as input }";
105
- string constant Relay = "{ uint portal, uint resources, #bytes as input }";
106
- string constant Dispatch = "{ uint portal, uint resources, #bytes as payload }";
107
- string constant Context = "{ bytes32 account, #bytes as state, #bytes as input }";
108
- string constant Recover = "{ uint handler, uint resources, bytes32 key, #bytes as witness }";
109
- string constant Annotation = "{ uint entity, #bytes as data }";
112
+ string constant Call = "uint target, uint resources, #bytes as payload";
113
+ string constant Step = "uint cmd, uint resources, #bytes as input";
114
+ string constant Relay = "uint portal, uint resources, #bytes as input";
115
+ string constant Dispatch = "uint portal, uint resources, #bytes as payload";
116
+ string constant Context = "bytes32 account, #bytes as state, #bytes as input";
117
+ string constant Recover = "uint handler, uint resources, bytes32 key, #bytes as witness";
118
+ string constant Annotation = "uint entity, #bytes as data";
110
119
 
111
120
  // Annotation payloads
112
121
 
113
- string constant Action = "{ uint action }";
114
- string constant Label = "{ bytes32 namespace, #string as name }";
115
- string constant Schema = "{ uint spec, #string as body, bytes32 name }";
122
+ string constant Action = "uint action";
123
+ string constant Label = "bytes32 namespace, #string as name";
124
+ string constant Schema = "uint spec, #string as body, bytes32 name";
116
125
  }
117
126
 
package/codec/Writers.sol CHANGED
@@ -73,6 +73,14 @@ library Writers {
73
73
  // Append helpers
74
74
  // -------------------------------------------------------------------------
75
75
 
76
+ /// @notice Append an empty block.
77
+ /// @param writer Destination writer.
78
+ /// @param key Block key.
79
+ function appendEmpty(Writer memory writer, bytes4 key) internal pure {
80
+ uint i = reserve(writer, Sizes.Header);
81
+ Blocks.writeEmpty(writer.dst, i, key);
82
+ }
83
+
76
84
  /// @notice Append arbitrary bytes to the writer.
77
85
  /// @param writer Destination writer; `i` is advanced by `data.length`.
78
86
  /// @param data Bytes to append.