@dev.sail.money/sailor 0.1.0-local → 1.0.0-38

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 (153) hide show
  1. package/AGENTS.md +139 -140
  2. package/LICENSE +21 -21
  3. package/README.md +428 -430
  4. package/docs/PERMISSION_MODEL.md +93 -93
  5. package/examples/permissions/BoundedApproveAndCallBatch.sol +179 -179
  6. package/examples/permissions/BoundedBet_Limitless_Base.sol +97 -97
  7. package/examples/permissions/BoundedBorrow_AaveV3_Arbitrum.sol +94 -94
  8. package/examples/permissions/BoundedPerp_GMXv2_Arbitrum.sol +154 -154
  9. package/examples/permissions/BoundedStake_Venice_Base.sol +85 -85
  10. package/examples/permissions/BoundedSupply_AaveV3_Arbitrum.sol +82 -82
  11. package/examples/permissions/BoundedSwap_UniswapV3_Base.sol +116 -116
  12. package/examples/permissions/BoundedSwap_UniswapV4_Unichain.sol +150 -150
  13. package/examples/permissions/BoundedTransfer_ERC20_Ethereum.sol +73 -73
  14. package/examples/permissions/BoundedVault_ERC4626_Base.sol +97 -97
  15. package/examples/permissions/README.md +79 -79
  16. package/examples/permissions/SailCalldata.sol +118 -118
  17. package/examples/permissions/foundry.toml +10 -10
  18. package/examples/permissions/interfaces/IBatchPermission.sol +38 -38
  19. package/examples/permissions/interfaces/IPermission.sol +18 -18
  20. package/package.json +44 -45
  21. package/packages/cli/README.md +34 -34
  22. package/packages/cli/dist/index.cjs +734 -705
  23. package/packages/cli/dist/server.cjs +627 -538
  24. package/packages/sdk/README.md +65 -65
  25. package/packages/sdk/dist/intelligence.d.ts +1 -1
  26. package/packages/sdk/dist/intelligence.js +1 -1
  27. package/packages/sdk/package.json +80 -80
  28. package/packages/ui/dist/assets/{add-BxpXfVWe.js → add-Dl1etsL9.js} +1 -1
  29. package/packages/ui/dist/assets/{all-wallets-BKTn_sWK.js → all-wallets-C0eHLOGG.js} +1 -1
  30. package/packages/ui/dist/assets/{app-store-CfuKbwxR.js → app-store-B-VMDEZ3.js} +1 -1
  31. package/packages/ui/dist/assets/{apple-BKSBbNYg.js → apple-DkDXzKns.js} +1 -1
  32. package/packages/ui/dist/assets/{arrow-bottom-D4bG6gZi.js → arrow-bottom-DtPzuS76.js} +1 -1
  33. package/packages/ui/dist/assets/{arrow-bottom-circle-BNTs1p0T.js → arrow-bottom-circle-D7odSAO8.js} +1 -1
  34. package/packages/ui/dist/assets/{arrow-left-2uee3vYv.js → arrow-left-zJV9tpx0.js} +1 -1
  35. package/packages/ui/dist/assets/{arrow-right-BktjMV6h.js → arrow-right-BOREfe7o.js} +1 -1
  36. package/packages/ui/dist/assets/{arrow-top-Izu28fX4.js → arrow-top-CipQc3Af.js} +1 -1
  37. package/packages/ui/dist/assets/{bank-USBaAyFM.js → bank-C5s7eoV5.js} +1 -1
  38. package/packages/ui/dist/assets/{basic-C_9KjTEH.js → basic-D2es4Vq8.js} +1 -1
  39. package/packages/ui/dist/assets/{browser-DAEMAKV7.js → browser-DITQWDC9.js} +1 -1
  40. package/packages/ui/dist/assets/{card-DT8yDkKN.js → card-C3DDkaYK.js} +1 -1
  41. package/packages/ui/dist/assets/{ccip-CkqfGSxX.js → ccip-UBXL3JiN.js} +1 -1
  42. package/packages/ui/dist/assets/{checkmark-bold-D2gjOQo2.js → checkmark-bold-D8yW0_K_.js} +1 -1
  43. package/packages/ui/dist/assets/{checkmark-CsgdEXFj.js → checkmark-ngef3MAl.js} +1 -1
  44. package/packages/ui/dist/assets/{chevron-bottom-tprFynYV.js → chevron-bottom-C56BipDR.js} +1 -1
  45. package/packages/ui/dist/assets/{chevron-left-D2Zj1gNB.js → chevron-left-BmIPtPl_.js} +1 -1
  46. package/packages/ui/dist/assets/{chevron-right-D1rRuAVe.js → chevron-right-BnySHQ8h.js} +1 -1
  47. package/packages/ui/dist/assets/{chevron-top-24dL1mbL.js → chevron-top-BDGZnNW3.js} +1 -1
  48. package/packages/ui/dist/assets/{chrome-store-Vy-5niYX.js → chrome-store-BYIqJZVF.js} +1 -1
  49. package/packages/ui/dist/assets/{clock-qBjLnVdJ.js → clock-Bl4mUHAM.js} +1 -1
  50. package/packages/ui/dist/assets/{close-DARDwgcu.js → close-B9rhEX6U.js} +1 -1
  51. package/packages/ui/dist/assets/{coinPlaceholder-BvpIbPlD.js → coinPlaceholder-1cO0FQsl.js} +1 -1
  52. package/packages/ui/dist/assets/{compass-BMTO0ayt.js → compass-7i-VuXu2.js} +1 -1
  53. package/packages/ui/dist/assets/{copy-PaXeRHza.js → copy-OqqXix2J.js} +1 -1
  54. package/packages/ui/dist/assets/{core-BFnStQd-.js → core-tX9kIIDJ.js} +3 -3
  55. package/packages/ui/dist/assets/cursor-BoyeQ9fN.js +3 -0
  56. package/packages/ui/dist/assets/{cursor-transparent-BEMdi-8q.js → cursor-transparent-5aoRH67u.js} +1 -1
  57. package/packages/ui/dist/assets/{desktop-CfuLLThw.js → desktop-BaPXK9R6.js} +1 -1
  58. package/packages/ui/dist/assets/{disconnect-DhwgJMiR.js → disconnect-LlK5K1CF.js} +1 -1
  59. package/packages/ui/dist/assets/{discord-po8qoN1s.js → discord-BdcQNWY_.js} +1 -1
  60. package/packages/ui/dist/assets/{etherscan-BEsz0_yx.js → etherscan-Bb-WxpO1.js} +1 -1
  61. package/packages/ui/dist/assets/{events-Bz33Unzu.js → events-DjdZr6no.js} +1 -1
  62. package/packages/ui/dist/assets/{exclamation-triangle-7CjTAGOQ.js → exclamation-triangle-COx4VtPV.js} +1 -1
  63. package/packages/ui/dist/assets/{extension-CmxjEWEt.js → extension-DF63DTWO.js} +1 -1
  64. package/packages/ui/dist/assets/{external-link-CmQ--bNS.js → external-link-DyghCkQu.js} +1 -1
  65. package/packages/ui/dist/assets/{facebook-CIBn9b65.js → facebook-Dcg4bZMR.js} +1 -1
  66. package/packages/ui/dist/assets/{fallback-DATyrQlb.js → fallback-DJIr_fH3.js} +1 -1
  67. package/packages/ui/dist/assets/{farcaster-OJ3Jasxg.js → farcaster-BkmV5HjO.js} +1 -1
  68. package/packages/ui/dist/assets/{filters-D4x09zeL.js → filters-DLHj1T_P.js} +1 -1
  69. package/packages/ui/dist/assets/{github-ZlIuMArp.js → github-BFDCgKrF.js} +1 -1
  70. package/packages/ui/dist/assets/{google-Gwg85sfv.js → google-C08SpmIy.js} +1 -1
  71. package/packages/ui/dist/assets/{help-circle-D1uOWYcX.js → help-circle-DU1IFmWp.js} +1 -1
  72. package/packages/ui/dist/assets/{id-C0-5UdYk.js → id-DtDRGf3L.js} +1 -1
  73. package/packages/ui/dist/assets/{image-D_DUsv8-.js → image-CgCXJEjT.js} +1 -1
  74. package/packages/ui/dist/assets/{index-izd7vu_r.js → index-8chM4S5Y.js} +1 -1
  75. package/packages/ui/dist/assets/{index-CrYzBWfD.js → index-B5sCtNuq.js} +1 -1
  76. package/packages/ui/dist/assets/{index-DdbJhIdl.js → index-BBfBEazf.js} +3 -3
  77. package/packages/ui/dist/assets/{index-BCzex_R6.js → index-BhXPwltt.js} +1 -1
  78. package/packages/ui/dist/assets/index-Cm05Py20.css +1 -0
  79. package/packages/ui/dist/assets/{index-DiojfeVM.js → index-D37bD6Yt.js} +1 -1
  80. package/packages/ui/dist/assets/index-DZfBh-cg.js +1775 -0
  81. package/packages/ui/dist/assets/{index.es-DdkHhQAj.js → index.es-BEcNQEn-.js} +4 -4
  82. package/packages/ui/dist/assets/{info-CiRd_kEG.js → info-ClsdYA4P.js} +1 -1
  83. package/packages/ui/dist/assets/{info-circle-ypxjqarK.js → info-circle-DJmn4Bsv.js} +1 -1
  84. package/packages/ui/dist/assets/{lightbulb-B-pxLxd8.js → lightbulb-CXSftjXS.js} +1 -1
  85. package/packages/ui/dist/assets/{mail-BYmicuVZ.js → mail-cdYKOl9P.js} +1 -1
  86. package/packages/ui/dist/assets/{metamask-sdk-Ccl6DG7Q.js → metamask-sdk-Co3aIEln.js} +1 -1
  87. package/packages/ui/dist/assets/{mobile-CtP5PqVT.js → mobile-DEHYlk8L.js} +1 -1
  88. package/packages/ui/dist/assets/{more-6C2733we.js → more-Cq_fo8pI.js} +1 -1
  89. package/packages/ui/dist/assets/{network-placeholder-CdhxMzqd.js → network-placeholder-_dLCK4xB.js} +1 -1
  90. package/packages/ui/dist/assets/{nftPlaceholder-DVmTWEAY.js → nftPlaceholder-Dz4HKEr4.js} +1 -1
  91. package/packages/ui/dist/assets/{off-DNYLughs.js → off-B1k1lhkr.js} +1 -1
  92. package/packages/ui/dist/assets/{parseSignature-Dq2B5Bu3.js → parseSignature-Cr0ptV2X.js} +1 -1
  93. package/packages/ui/dist/assets/{play-store-D7Qut5ta.js → play-store-lYqe4eeL.js} +1 -1
  94. package/packages/ui/dist/assets/{plus-kqMyjt3q.js → plus-QMgh1krr.js} +1 -1
  95. package/packages/ui/dist/assets/{qr-code-DiUCWRbz.js → qr-code-ClVHbZWN.js} +1 -1
  96. package/packages/ui/dist/assets/{recycle-horizontal-Boe3XiS-.js → recycle-horizontal-CxGYnWid.js} +1 -1
  97. package/packages/ui/dist/assets/{refresh-CrBgBQYO.js → refresh-CPysMza_.js} +1 -1
  98. package/packages/ui/dist/assets/{reown-logo-CFZCCHSx.js → reown-logo-OoL_zJd0.js} +1 -1
  99. package/packages/ui/dist/assets/{search-ChTDrghU.js → search-2GaRbf1I.js} +1 -1
  100. package/packages/ui/dist/assets/{secp256k1-DAV5Q_FR.js → secp256k1-BrB8qSSy.js} +1 -1
  101. package/packages/ui/dist/assets/{send-DLFbBFe1.js → send-CC2UuIfD.js} +1 -1
  102. package/packages/ui/dist/assets/{swapHorizontal-BEs3emfG.js → swapHorizontal-BRqYwsqT.js} +1 -1
  103. package/packages/ui/dist/assets/{swapHorizontalBold-CC-Hfa7W.js → swapHorizontalBold-Bj0GSRq9.js} +1 -1
  104. package/packages/ui/dist/assets/{swapHorizontalMedium-BmR0H8DC.js → swapHorizontalMedium-oLOjpU2A.js} +1 -1
  105. package/packages/ui/dist/assets/{swapHorizontalRoundedBold-BdP5NGIH.js → swapHorizontalRoundedBold-TJ652QXb.js} +1 -1
  106. package/packages/ui/dist/assets/{swapVertical-CPrGEJPY.js → swapVertical-e0NLyV3x.js} +1 -1
  107. package/packages/ui/dist/assets/{telegram-CxNoZ80Q.js → telegram-WhJHVeoU.js} +1 -1
  108. package/packages/ui/dist/assets/{three-dots-BRa6SBpL.js → three-dots-BlBAOyW-.js} +1 -1
  109. package/packages/ui/dist/assets/{twitch-BC338bG5.js → twitch-BH7vWmPc.js} +1 -1
  110. package/packages/ui/dist/assets/{twitterIcon-BGZmt2i9.js → twitterIcon-As0Nkanp.js} +1 -1
  111. package/packages/ui/dist/assets/{verify-CEstW0zw.js → verify-BEJ0QuLl.js} +1 -1
  112. package/packages/ui/dist/assets/{verify-filled-OkZb0weU.js → verify-filled-B8Ww2N7z.js} +1 -1
  113. package/packages/ui/dist/assets/{w3m-modal-pS09ECwE.js → w3m-modal-5rOSZgOR.js} +1 -1
  114. package/packages/ui/dist/assets/{wallet-BXVKCgC9.js → wallet-CAfC3aml.js} +1 -1
  115. package/packages/ui/dist/assets/{wallet-placeholder-C_kNhB1c.js → wallet-placeholder-4RZI464Z.js} +1 -1
  116. package/packages/ui/dist/assets/{walletconnect-CRKIuUHH.js → walletconnect-BiltKqAe.js} +1 -1
  117. package/packages/ui/dist/assets/{warning-circle-DB2NnwlJ.js → warning-circle-CI4jqpHo.js} +1 -1
  118. package/packages/ui/dist/assets/{x-DT4RmwL5.js → x-FBttjBWO.js} +1 -1
  119. package/packages/ui/dist/index.html +14 -14
  120. package/scripts/check-docs.mjs +262 -262
  121. package/scripts/check-init.mjs +108 -108
  122. package/templates/custom-mandate/.sail/contracts/interfaces/IPermission.sol +18 -18
  123. package/templates/custom-mandate/README.md +116 -116
  124. package/templates/custom-mandate/foundry.toml +8 -8
  125. package/templates/custom-mandate/mandates/BoundedCallPermission.sol +41 -41
  126. package/templates/custom-mandate/mandates/README.md +16 -16
  127. package/templates/custom-mandate/mandates/SailCalldata.sol +118 -118
  128. package/templates/default/.cursor/rules +25 -25
  129. package/templates/default/.env.example +20 -20
  130. package/templates/default/.github/workflows/agent-tick.yml +33 -33
  131. package/templates/default/.sail/README.md +13 -13
  132. package/templates/default/.sail/config.json +10 -10
  133. package/templates/default/AGENTS.md +171 -171
  134. package/templates/default/CLAUDE.md +2 -2
  135. package/templates/default/README.md +16 -16
  136. package/templates/default/_gitignore +13 -13
  137. package/templates/default/docs/PERMISSION_MODEL.md +93 -93
  138. package/templates/default/examples/dca/README.md +16 -16
  139. package/templates/default/examples/dca/agent.ts +174 -174
  140. package/templates/default/examples/dca/mandate.ts +45 -45
  141. package/templates/default/package.json +17 -17
  142. package/templates/default/src/agent.ts +37 -37
  143. package/templates/default/src/config.ts +24 -24
  144. package/templates/default/src/mandate.ts +22 -22
  145. package/templates/default/tsconfig.json +17 -17
  146. package/templates/default/ui/README.md +3 -3
  147. package/templates/lifi-permissions/LifiBoundedApprovePermissionCloneable.sol +84 -84
  148. package/templates/lifi-permissions/LifiDiamondSwapPermissionCloneable.sol +97 -97
  149. package/templates/lifi-permissions/README.md +53 -53
  150. package/packages/ui/dist/assets/cursor-BDvw-B17.js +0 -3
  151. package/packages/ui/dist/assets/index-BUhrHLpY.js +0 -1775
  152. package/packages/ui/dist/assets/index-Cq02kQmy.css +0 -1
  153. package/scripts/postinstall.js +0 -81
@@ -1,97 +1,97 @@
1
- // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
3
-
4
- // ─────────────────────────────────────────────────────────────────────────────
5
- // Protocol : ERC-4626 (tokenized vault standard)
6
- // Version : Standard EIP-4626 interface (version-agnostic)
7
- // Chain : Base mainnet (8453) — reference vault below; works on any EVM
8
- // Target : Reference vault — Moonwell Flagship USDC (MetaMorpho, ERC-4626)
9
- // 0xc1256Ae5FF1cf2719D4937adb3bbCCab2E00A2Ca (verified ERC-4626 on Base: asset()=USDC)
10
- // Written against the STANDARD interface, so it generalizes to any 4626 vault.
11
- //
12
- // ENFORCES ON-CHAIN (kernel calls evaluate() on every dispatch; false ⇒ dispatch blocked):
13
- // deposit(uint256 assets,address receiver) selector 0x6e553f65
14
- // • target (the vault) must be in ALLOWED_VAULTS
15
- // • assets ≤ MAX_DEPOSIT_ASSETS
16
- // • receiver must equal ctx.account (the SMA — shares cannot be minted to another address)
17
- // withdraw(uint256 assets,address receiver,address owner) selector 0xb460af94
18
- // redeem(uint256 shares,address receiver,address owner) selector 0xba087652
19
- // • target (the vault) must be in ALLOWED_VAULTS
20
- // • receiver must equal ctx.account (assets cannot be sent elsewhere)
21
- // • owner must equal ctx.account (cannot burn shares the SMA merely has allowance over)
22
- //
23
- // AGENT-ENFORCED / NOT BOUNDED HERE (off-chain — can change without redeploying this contract):
24
- // • Which vault within ALLOWED_VAULTS to use, and deposit/withdraw timing/cadence
25
- // • Withdraw/redeem amount (only the receiver/owner are bound, not the size — the SMA can
26
- // always withdraw its own position in full; add a cap here if your strategy needs one)
27
- // • Underlying ASSET: enforced TRANSITIVELY via ALLOWED_VAULTS, not from calldata. A 4626
28
- // vault's asset() is fixed at deploy; the deposit/withdraw calldata carries NO asset field,
29
- // so the asset cannot be read from it. Allowlist only vaults whose asset() you have verified.
30
- //
31
- // VERIFY BEFORE USE:
32
- // • Selectors are the EIP-4626 standard (verified via `cast sig`): deposit 0x6e553f65,
33
- // withdraw 0xb460af94, redeem 0xba087652. (mint(uint256,address)=0x94bf804d is NOT gated
34
- // here — add it if your agent mints shares by exact share count.)
35
- // • Confirm each allowlisted vault actually implements ERC-4626 and its asset() is what you
36
- // expect (e.g. on the reference vault, asset() == USDC 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913).
37
- // • A deposit also needs a prior ERC-20 approve of the vault — gate that with a separate
38
- // approve permission (see BoundedApproveAndCallBatch.sol for the atomic approve→call→reset pattern).
39
- // • MAX_DEPOSIT_ASSETS is in the asset's base units (USDC = 6 decimals).
40
- // ─────────────────────────────────────────────────────────────────────────────
41
-
42
- import {IPermission, Context} from "@sail/interfaces/IPermission.sol";
43
-
44
- contract BoundedVault_ERC4626_Base is IPermission {
45
- bytes32 private constant DISCRIMINATOR = keccak256("BoundedVault_ERC4626_Base");
46
-
47
- mapping(address => bool) public isAllowedVault;
48
- uint256 public immutable MAX_DEPOSIT_ASSETS;
49
-
50
- // EIP-4626 standard selectors
51
- bytes4 private constant SEL_DEPOSIT = 0x6e553f65; // deposit(uint256,address)
52
- bytes4 private constant SEL_WITHDRAW = 0xb460af94; // withdraw(uint256,address,address)
53
- bytes4 private constant SEL_REDEEM = 0xba087652; // redeem(uint256,address,address)
54
-
55
- /// @param allowedVaults ERC-4626 vaults the agent may deposit into / withdraw from
56
- /// @param maxDepositAssets Per-deposit cap in the asset's base units (must be > 0)
57
- constructor(address[] memory allowedVaults, uint256 maxDepositAssets) {
58
- require(allowedVaults.length > 0, "empty vault allowlist");
59
- require(maxDepositAssets > 0, "zero deposit cap");
60
- MAX_DEPOSIT_ASSETS = maxDepositAssets;
61
- for (uint256 i = 0; i < allowedVaults.length; i++) {
62
- require(allowedVaults[i] != address(0), "zero vault address");
63
- isAllowedVault[allowedVaults[i]] = true;
64
- }
65
- }
66
-
67
- function evaluate(bytes calldata txData, Context calldata ctx) external view returns (bool) {
68
- if (!isAllowedVault[ctx.target]) return false;
69
- if (txData.length < 4) return false;
70
-
71
- // deposit(uint256 assets, address receiver)
72
- if (ctx.selector == SEL_DEPOSIT) {
73
- if (txData.length < 4 + 2 * 32) return false;
74
- (uint256 assets, address receiver) = abi.decode(txData[4:], (uint256, address));
75
- if (assets > MAX_DEPOSIT_ASSETS) return false;
76
- if (receiver != ctx.account) return false; // shares must accrue to the SMA
77
- return true;
78
- }
79
-
80
- // withdraw(uint256 assets, address receiver, address owner)
81
- // redeem(uint256 shares, address receiver, address owner)
82
- // Identical parameter layout: (uint256, address, address).
83
- // Amount (assets/shares) is NOT bounded — the SMA can always exit its full position.
84
- // Add a maxWithdrawAssets constructor param and check here if your strategy needs a cap.
85
- if (ctx.selector == SEL_WITHDRAW || ctx.selector == SEL_REDEEM) {
86
- if (txData.length < 4 + 3 * 32) return false;
87
- (, address receiver, address owner) = abi.decode(txData[4:], (uint256, address, address));
88
- if (receiver != ctx.account) return false; // assets must return to the SMA
89
- if (owner != ctx.account) return false; // only the SMA's own shares may be burned
90
- return true;
91
- }
92
-
93
- return false;
94
- }
95
-
96
- function discriminator() external pure returns (bytes32) { return DISCRIMINATOR; }
97
- }
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity 0.8.26;
3
+
4
+ // ─────────────────────────────────────────────────────────────────────────────
5
+ // Protocol : ERC-4626 (tokenized vault standard)
6
+ // Version : Standard EIP-4626 interface (version-agnostic)
7
+ // Chain : Base mainnet (8453) — reference vault below; works on any EVM
8
+ // Target : Reference vault — Moonwell Flagship USDC (MetaMorpho, ERC-4626)
9
+ // 0xc1256Ae5FF1cf2719D4937adb3bbCCab2E00A2Ca (verified ERC-4626 on Base: asset()=USDC)
10
+ // Written against the STANDARD interface, so it generalizes to any 4626 vault.
11
+ //
12
+ // ENFORCES ON-CHAIN (kernel calls evaluate() on every dispatch; false ⇒ dispatch blocked):
13
+ // deposit(uint256 assets,address receiver) selector 0x6e553f65
14
+ // • target (the vault) must be in ALLOWED_VAULTS
15
+ // • assets ≤ MAX_DEPOSIT_ASSETS
16
+ // • receiver must equal ctx.account (the SMA — shares cannot be minted to another address)
17
+ // withdraw(uint256 assets,address receiver,address owner) selector 0xb460af94
18
+ // redeem(uint256 shares,address receiver,address owner) selector 0xba087652
19
+ // • target (the vault) must be in ALLOWED_VAULTS
20
+ // • receiver must equal ctx.account (assets cannot be sent elsewhere)
21
+ // • owner must equal ctx.account (cannot burn shares the SMA merely has allowance over)
22
+ //
23
+ // AGENT-ENFORCED / NOT BOUNDED HERE (off-chain — can change without redeploying this contract):
24
+ // • Which vault within ALLOWED_VAULTS to use, and deposit/withdraw timing/cadence
25
+ // • Withdraw/redeem amount (only the receiver/owner are bound, not the size — the SMA can
26
+ // always withdraw its own position in full; add a cap here if your strategy needs one)
27
+ // • Underlying ASSET: enforced TRANSITIVELY via ALLOWED_VAULTS, not from calldata. A 4626
28
+ // vault's asset() is fixed at deploy; the deposit/withdraw calldata carries NO asset field,
29
+ // so the asset cannot be read from it. Allowlist only vaults whose asset() you have verified.
30
+ //
31
+ // VERIFY BEFORE USE:
32
+ // • Selectors are the EIP-4626 standard (verified via `cast sig`): deposit 0x6e553f65,
33
+ // withdraw 0xb460af94, redeem 0xba087652. (mint(uint256,address)=0x94bf804d is NOT gated
34
+ // here — add it if your agent mints shares by exact share count.)
35
+ // • Confirm each allowlisted vault actually implements ERC-4626 and its asset() is what you
36
+ // expect (e.g. on the reference vault, asset() == USDC 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913).
37
+ // • A deposit also needs a prior ERC-20 approve of the vault — gate that with a separate
38
+ // approve permission (see BoundedApproveAndCallBatch.sol for the atomic approve→call→reset pattern).
39
+ // • MAX_DEPOSIT_ASSETS is in the asset's base units (USDC = 6 decimals).
40
+ // ─────────────────────────────────────────────────────────────────────────────
41
+
42
+ import {IPermission, Context} from "@sail/interfaces/IPermission.sol";
43
+
44
+ contract BoundedVault_ERC4626_Base is IPermission {
45
+ bytes32 private constant DISCRIMINATOR = keccak256("BoundedVault_ERC4626_Base");
46
+
47
+ mapping(address => bool) public isAllowedVault;
48
+ uint256 public immutable MAX_DEPOSIT_ASSETS;
49
+
50
+ // EIP-4626 standard selectors
51
+ bytes4 private constant SEL_DEPOSIT = 0x6e553f65; // deposit(uint256,address)
52
+ bytes4 private constant SEL_WITHDRAW = 0xb460af94; // withdraw(uint256,address,address)
53
+ bytes4 private constant SEL_REDEEM = 0xba087652; // redeem(uint256,address,address)
54
+
55
+ /// @param allowedVaults ERC-4626 vaults the agent may deposit into / withdraw from
56
+ /// @param maxDepositAssets Per-deposit cap in the asset's base units (must be > 0)
57
+ constructor(address[] memory allowedVaults, uint256 maxDepositAssets) {
58
+ require(allowedVaults.length > 0, "empty vault allowlist");
59
+ require(maxDepositAssets > 0, "zero deposit cap");
60
+ MAX_DEPOSIT_ASSETS = maxDepositAssets;
61
+ for (uint256 i = 0; i < allowedVaults.length; i++) {
62
+ require(allowedVaults[i] != address(0), "zero vault address");
63
+ isAllowedVault[allowedVaults[i]] = true;
64
+ }
65
+ }
66
+
67
+ function evaluate(bytes calldata txData, Context calldata ctx) external view returns (bool) {
68
+ if (!isAllowedVault[ctx.target]) return false;
69
+ if (txData.length < 4) return false;
70
+
71
+ // deposit(uint256 assets, address receiver)
72
+ if (ctx.selector == SEL_DEPOSIT) {
73
+ if (txData.length < 4 + 2 * 32) return false;
74
+ (uint256 assets, address receiver) = abi.decode(txData[4:], (uint256, address));
75
+ if (assets > MAX_DEPOSIT_ASSETS) return false;
76
+ if (receiver != ctx.account) return false; // shares must accrue to the SMA
77
+ return true;
78
+ }
79
+
80
+ // withdraw(uint256 assets, address receiver, address owner)
81
+ // redeem(uint256 shares, address receiver, address owner)
82
+ // Identical parameter layout: (uint256, address, address).
83
+ // Amount (assets/shares) is NOT bounded — the SMA can always exit its full position.
84
+ // Add a maxWithdrawAssets constructor param and check here if your strategy needs a cap.
85
+ if (ctx.selector == SEL_WITHDRAW || ctx.selector == SEL_REDEEM) {
86
+ if (txData.length < 4 + 3 * 32) return false;
87
+ (, address receiver, address owner) = abi.decode(txData[4:], (uint256, address, address));
88
+ if (receiver != ctx.account) return false; // assets must return to the SMA
89
+ if (owner != ctx.account) return false; // only the SMA's own shares may be burned
90
+ return true;
91
+ }
92
+
93
+ return false;
94
+ }
95
+
96
+ function discriminator() external pure returns (bytes32) { return DISCRIMINATOR; }
97
+ }
@@ -1,79 +1,79 @@
1
- # Permission Examples
2
-
3
- These are example permission contracts for common DeFi protocols, maintained by Sailor.
4
-
5
- **They are not part of Sail Protocol. They are not audited by Sail. They are not a supported
6
- or exhaustive set.** The protocol accepts any contract implementing IPermission — these examples
7
- teach the bounding pattern for specific protocols so you (and your AI agent) can adapt them or
8
- write your own.
9
-
10
- ## Permissions are only as strong as the protocol is on-chain
11
-
12
- A permission is evaluated by the kernel on every dispatch — but it can only see what happens
13
- on-chain. For venues with off-chain order matching (e.g. Polymarket, Hyperliquid), a permission
14
- can constrain deposits, withdrawals, and sub-accounts, but NOT the orders your agent signs
15
- off-chain. Prefer fully on-chain venues — Uniswap, Aave, GMX, Synthetix, Limitless — where every
16
- action passes through the kernel and your bounds actually hold.
17
-
18
- ## Permissions are protocol- and version-specific
19
-
20
- Calldata differs by protocol and by version. A Uniswap V3 swap is a different decode than a
21
- Uniswap V4 swap. Use the example closest to YOUR exact protocol+version+chain, verify the decode
22
- against the protocol's real ABI, and confirm what it enforces before deploying.
23
-
24
- You own what you deploy.
25
-
26
- ---
27
-
28
- ## Examples
29
-
30
- Each header has two blocks: **ENFORCES ON-CHAIN** (bounds the kernel checks in `evaluate()` on
31
- every dispatch — these actually hold) and **AGENT-ENFORCED / NOT BOUNDED HERE** (left to your
32
- off-chain agent code — these do *not* hold on-chain). Read both before deploying.
33
-
34
- | File | Protocol | Version | Chain | Status |
35
- |---|---|---|---|---|
36
- | `BoundedSwap_UniswapV3_Base.sol` | Uniswap Swap | V3 SwapRouter02 | Base | Full decode |
37
- | `BoundedSwap_UniswapV4_Unichain.sol` | Uniswap Swap | V4 Universal Router | Unichain | Partial decode — see header |
38
- | `BoundedBorrow_AaveV3_Arbitrum.sol` | Aave Borrow | V3 Pool | Arbitrum | Full decode |
39
- | `BoundedSupply_AaveV3_Arbitrum.sol` | Aave Supply | V3 Pool | Arbitrum | Full decode |
40
- | `BoundedVault_ERC4626_Base.sol` | ERC-4626 Vault deposit/withdraw | EIP-4626 standard | Base (any EVM) | Full decode |
41
- | `BoundedStake_Venice_Base.sol` | Venice (VVV) staking | sVVV staking | Base | Full decode |
42
- | `BoundedTransfer_ERC20_Ethereum.sol` | ERC-20 Transfer | — | Ethereum (any EVM) | Full decode |
43
- | `BoundedPerp_GMXv2_Arbitrum.sol` | GMX Perpetuals | V2 ExchangeRouter | Arbitrum | Reference pattern — verify selector/struct/router against live GMX ABI (see header) |
44
- | `BoundedBet_Limitless_Base.sol` | Limitless Prediction | CTF Exchange | Base | UNVERIFIED ABI — see header |
45
- | `BoundedApproveAndCallBatch.sol` | Atomic approve→call→reset | Sail `IBatchPermission` | Any selective kernel | Full decode — batch-only (see note below) |
46
-
47
- The `interfaces/` directory holds `IPermission.sol` (single-call permissions) and
48
- `IBatchPermission.sol` (batch permissions — see the batch example).
49
-
50
- ## Using these examples
51
-
52
- Each file is self-contained and explains in its header exactly what is and is not enforced.
53
- Read the header before deploying.
54
-
55
- To compile:
56
- ```bash
57
- forge build
58
- ```
59
-
60
- To deploy within a Sailor project (copy the .sol file to `mandates/` first):
61
- ```bash
62
- sailor mandate deploy --contract <Name> --args '[...]' --attach --sma <SMA>
63
- ```
64
-
65
- ## Verify before you authorize
66
-
67
- Prove a permission accepts the calls you want and rejects the ones you don't — before paying
68
- registration gas — with `sailor mandate simulate` (off-chain `eth_call`, no gas, signs nothing):
69
- ```bash
70
- sailor mandate simulate --address <permission> --calls calls.json
71
- ```
72
- Every example here was checked this way (valid calls PASS, out-of-bounds calls FAIL).
73
-
74
- **Batch permissions are different.** `BoundedApproveAndCallBatch.sol` implements `IBatchPermission`
75
- and is gated by the kernel's `dispatchBatch`, not single `dispatch` — its single-call `evaluate()`
76
- deliberately returns `false`. `sailor mandate simulate` only probes single `evaluate()`, so it
77
- **cannot** verify a batch permission. Until simulate gains a batch mode, verify a batch permission
78
- by calling its `evaluateBatch(calls, ctx)` view directly (e.g. `cast call`) with the call sequences
79
- you expect to pass and fail — same fail-closed `eth_call` semantics, the correct entrypoint.
1
+ # Permission Examples
2
+
3
+ These are example permission contracts for common DeFi protocols, maintained by Sailor.
4
+
5
+ **They are not part of Sail Protocol. They are not audited by Sail. They are not a supported
6
+ or exhaustive set.** The protocol accepts any contract implementing IPermission — these examples
7
+ teach the bounding pattern for specific protocols so you (and your AI agent) can adapt them or
8
+ write your own.
9
+
10
+ ## Permissions are only as strong as the protocol is on-chain
11
+
12
+ A permission is evaluated by the kernel on every dispatch — but it can only see what happens
13
+ on-chain. For venues with off-chain order matching (e.g. Polymarket, Hyperliquid), a permission
14
+ can constrain deposits, withdrawals, and sub-accounts, but NOT the orders your agent signs
15
+ off-chain. Prefer fully on-chain venues — Uniswap, Aave, GMX, Synthetix, Limitless — where every
16
+ action passes through the kernel and your bounds actually hold.
17
+
18
+ ## Permissions are protocol- and version-specific
19
+
20
+ Calldata differs by protocol and by version. A Uniswap V3 swap is a different decode than a
21
+ Uniswap V4 swap. Use the example closest to YOUR exact protocol+version+chain, verify the decode
22
+ against the protocol's real ABI, and confirm what it enforces before deploying.
23
+
24
+ You own what you deploy.
25
+
26
+ ---
27
+
28
+ ## Examples
29
+
30
+ Each header has two blocks: **ENFORCES ON-CHAIN** (bounds the kernel checks in `evaluate()` on
31
+ every dispatch — these actually hold) and **AGENT-ENFORCED / NOT BOUNDED HERE** (left to your
32
+ off-chain agent code — these do *not* hold on-chain). Read both before deploying.
33
+
34
+ | File | Protocol | Version | Chain | Status |
35
+ |---|---|---|---|---|
36
+ | `BoundedSwap_UniswapV3_Base.sol` | Uniswap Swap | V3 SwapRouter02 | Base | Full decode |
37
+ | `BoundedSwap_UniswapV4_Unichain.sol` | Uniswap Swap | V4 Universal Router | Unichain | Partial decode — see header |
38
+ | `BoundedBorrow_AaveV3_Arbitrum.sol` | Aave Borrow | V3 Pool | Arbitrum | Full decode |
39
+ | `BoundedSupply_AaveV3_Arbitrum.sol` | Aave Supply | V3 Pool | Arbitrum | Full decode |
40
+ | `BoundedVault_ERC4626_Base.sol` | ERC-4626 Vault deposit/withdraw | EIP-4626 standard | Base (any EVM) | Full decode |
41
+ | `BoundedStake_Venice_Base.sol` | Venice (VVV) staking | sVVV staking | Base | Full decode |
42
+ | `BoundedTransfer_ERC20_Ethereum.sol` | ERC-20 Transfer | — | Ethereum (any EVM) | Full decode |
43
+ | `BoundedPerp_GMXv2_Arbitrum.sol` | GMX Perpetuals | V2 ExchangeRouter | Arbitrum | Reference pattern — verify selector/struct/router against live GMX ABI (see header) |
44
+ | `BoundedBet_Limitless_Base.sol` | Limitless Prediction | CTF Exchange | Base | UNVERIFIED ABI — see header |
45
+ | `BoundedApproveAndCallBatch.sol` | Atomic approve→call→reset | Sail `IBatchPermission` | Any selective kernel | Full decode — batch-only (see note below) |
46
+
47
+ The `interfaces/` directory holds `IPermission.sol` (single-call permissions) and
48
+ `IBatchPermission.sol` (batch permissions — see the batch example).
49
+
50
+ ## Using these examples
51
+
52
+ Each file is self-contained and explains in its header exactly what is and is not enforced.
53
+ Read the header before deploying.
54
+
55
+ To compile:
56
+ ```bash
57
+ forge build
58
+ ```
59
+
60
+ To deploy within a Sailor project (copy the .sol file to `mandates/` first):
61
+ ```bash
62
+ sailor mandate deploy --contract <Name> --args '[...]' --attach --sma <SMA>
63
+ ```
64
+
65
+ ## Verify before you authorize
66
+
67
+ Prove a permission accepts the calls you want and rejects the ones you don't — before paying
68
+ registration gas — with `sailor mandate simulate` (off-chain `eth_call`, no gas, signs nothing):
69
+ ```bash
70
+ sailor mandate simulate --address <permission> --calls calls.json
71
+ ```
72
+ Every example here was checked this way (valid calls PASS, out-of-bounds calls FAIL).
73
+
74
+ **Batch permissions are different.** `BoundedApproveAndCallBatch.sol` implements `IBatchPermission`
75
+ and is gated by the kernel's `dispatchBatch`, not single `dispatch` — its single-call `evaluate()`
76
+ deliberately returns `false`. `sailor mandate simulate` only probes single `evaluate()`, so it
77
+ **cannot** verify a batch permission. Until simulate gains a batch mode, verify a batch permission
78
+ by calling its `evaluateBatch(calls, ctx)` view directly (e.g. `cast call`) with the call sequences
79
+ you expect to pass and fail — same fail-closed `eth_call` semantics, the correct entrypoint.
@@ -1,118 +1,118 @@
1
- // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
3
-
4
- // ─────────────────────────────────────────────────────────────────────────────
5
- // SailCalldata — safe static-parameter extraction for IPermission.evaluate()
6
- //
7
- // PROBLEM
8
- // evaluate(bytes calldata txData, Context calldata ctx) receives raw ABI-
9
- // encoded calldata. Extracting parameters by hand is the most dangerous part
10
- // of permission writing:
11
- // • Forgetting the length check before decoding → silent wrong value or
12
- // revert instead of clean `return false`.
13
- // • Wrong slot index → decoding the wrong parameter (type-checks, still wrong).
14
- // • Truncation bugs when casting uint256 slots to address (padding bits).
15
- //
16
- // SOLUTION
17
- // This library centralises the three operations into named helpers:
18
- // 1. hasParams() — explicit length guard (call once, at the top of evaluate)
19
- // 2. asAddress() — extract + mask to 20 bytes
20
- // 3. asUint256(), asInt256(), asBytes32(), asBool(), asUint128() — typed slots
21
- //
22
- // All helpers are view/pure and add zero gas overhead beyond the slice itself.
23
- //
24
- // USAGE (replace abi.decode pattern)
25
- //
26
- // // Before:
27
- // if (txData.length < 4 + 3 * 32) return false;
28
- // (address asset, uint256 amount, address onBehalfOf) =
29
- // abi.decode(txData[4:], (address, uint256, address));
30
- //
31
- // // After:
32
- // if (!SailCalldata.hasParams(txData, 3)) return false;
33
- // address asset = SailCalldata.asAddress(txData, 0);
34
- // uint256 amount = SailCalldata.asUint256(txData, 1);
35
- // address onBehalfOf = SailCalldata.asAddress(txData, 2);
36
- //
37
- // LIMITATIONS
38
- // Only covers static (fixed-size) ABI types. Dynamic types (bytes, string,
39
- // arrays) use pointer indirection — decode them with abi.decode(txData[4:], ...)
40
- // after the hasParams() guard, or write a dedicated extractor.
41
- //
42
- // SLOT INDEXING
43
- // Slots are 0-indexed from the first constructor parameter (after the 4-byte
44
- // selector). Slot 0 = bytes [4..35], slot 1 = bytes [36..67], etc.
45
- // ─────────────────────────────────────────────────────────────────────────────
46
-
47
- library SailCalldata {
48
- // ── Guards ────────────────────────────────────────────────────────────────
49
-
50
- /// @notice Returns true when txData is long enough to hold `params` static
51
- /// 32-byte ABI slots after the 4-byte selector.
52
- /// @dev Call this once at the top of evaluate() before any slot access.
53
- /// Returns false (not revert) so evaluate() can return false cleanly.
54
- function hasParams(bytes calldata txData, uint256 params) internal pure returns (bool) {
55
- return txData.length >= 4 + params * 32;
56
- }
57
-
58
- // ── Static-type extractors ────────────────────────────────────────────────
59
- // All assume hasParams() has already been checked for the relevant slot.
60
- // Accessing an out-of-bounds slot will cause the calldata slice to revert —
61
- // always guard with hasParams() first.
62
-
63
- /// @notice Extract an address from ABI slot `i` (0-indexed after selector).
64
- /// Masks to 20 bytes, discarding the ABI zero-padding in the upper 12.
65
- function asAddress(bytes calldata txData, uint256 i) internal pure returns (address) {
66
- return address(uint160(uint256(bytes32(txData[4 + i * 32 : 4 + (i + 1) * 32]))));
67
- }
68
-
69
- /// @notice Extract a uint256 from ABI slot `i`.
70
- function asUint256(bytes calldata txData, uint256 i) internal pure returns (uint256) {
71
- return uint256(bytes32(txData[4 + i * 32 : 4 + (i + 1) * 32]));
72
- }
73
-
74
- /// @notice Extract an int256 from ABI slot `i`.
75
- function asInt256(bytes calldata txData, uint256 i) internal pure returns (int256) {
76
- return int256(uint256(bytes32(txData[4 + i * 32 : 4 + (i + 1) * 32])));
77
- }
78
-
79
- /// @notice Extract a bytes32 from ABI slot `i`.
80
- function asBytes32(bytes calldata txData, uint256 i) internal pure returns (bytes32) {
81
- return bytes32(txData[4 + i * 32 : 4 + (i + 1) * 32]);
82
- }
83
-
84
- /// @notice Extract a bool from ABI slot `i` (true when slot value is non-zero).
85
- function asBool(bytes calldata txData, uint256 i) internal pure returns (bool) {
86
- return asUint256(txData, i) != 0;
87
- }
88
-
89
- /// @notice Extract a uint128 from ABI slot `i` (lower 128 bits of the slot).
90
- function asUint128(bytes calldata txData, uint256 i) internal pure returns (uint128) {
91
- return uint128(asUint256(txData, i));
92
- }
93
-
94
- /// @notice Extract a uint64 from ABI slot `i`.
95
- function asUint64(bytes calldata txData, uint256 i) internal pure returns (uint64) {
96
- return uint64(asUint256(txData, i));
97
- }
98
-
99
- /// @notice Extract a uint32 from ABI slot `i`.
100
- function asUint32(bytes calldata txData, uint256 i) internal pure returns (uint32) {
101
- return uint32(asUint256(txData, i));
102
- }
103
-
104
- /// @notice Extract a uint24 from ABI slot `i` (e.g. Uniswap fee tier).
105
- function asUint24(bytes calldata txData, uint256 i) internal pure returns (uint24) {
106
- return uint24(asUint256(txData, i));
107
- }
108
-
109
- /// @notice Extract a uint16 from ABI slot `i` (e.g. Aave referral code).
110
- function asUint16(bytes calldata txData, uint256 i) internal pure returns (uint16) {
111
- return uint16(asUint256(txData, i));
112
- }
113
-
114
- /// @notice Extract bytes4 (a function selector) from ABI slot `i`.
115
- function asBytes4(bytes calldata txData, uint256 i) internal pure returns (bytes4) {
116
- return bytes4(asBytes32(txData, i));
117
- }
118
- }
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity 0.8.26;
3
+
4
+ // ─────────────────────────────────────────────────────────────────────────────
5
+ // SailCalldata — safe static-parameter extraction for IPermission.evaluate()
6
+ //
7
+ // PROBLEM
8
+ // evaluate(bytes calldata txData, Context calldata ctx) receives raw ABI-
9
+ // encoded calldata. Extracting parameters by hand is the most dangerous part
10
+ // of permission writing:
11
+ // • Forgetting the length check before decoding → silent wrong value or
12
+ // revert instead of clean `return false`.
13
+ // • Wrong slot index → decoding the wrong parameter (type-checks, still wrong).
14
+ // • Truncation bugs when casting uint256 slots to address (padding bits).
15
+ //
16
+ // SOLUTION
17
+ // This library centralises the three operations into named helpers:
18
+ // 1. hasParams() — explicit length guard (call once, at the top of evaluate)
19
+ // 2. asAddress() — extract + mask to 20 bytes
20
+ // 3. asUint256(), asInt256(), asBytes32(), asBool(), asUint128() — typed slots
21
+ //
22
+ // All helpers are view/pure and add zero gas overhead beyond the slice itself.
23
+ //
24
+ // USAGE (replace abi.decode pattern)
25
+ //
26
+ // // Before:
27
+ // if (txData.length < 4 + 3 * 32) return false;
28
+ // (address asset, uint256 amount, address onBehalfOf) =
29
+ // abi.decode(txData[4:], (address, uint256, address));
30
+ //
31
+ // // After:
32
+ // if (!SailCalldata.hasParams(txData, 3)) return false;
33
+ // address asset = SailCalldata.asAddress(txData, 0);
34
+ // uint256 amount = SailCalldata.asUint256(txData, 1);
35
+ // address onBehalfOf = SailCalldata.asAddress(txData, 2);
36
+ //
37
+ // LIMITATIONS
38
+ // Only covers static (fixed-size) ABI types. Dynamic types (bytes, string,
39
+ // arrays) use pointer indirection — decode them with abi.decode(txData[4:], ...)
40
+ // after the hasParams() guard, or write a dedicated extractor.
41
+ //
42
+ // SLOT INDEXING
43
+ // Slots are 0-indexed from the first constructor parameter (after the 4-byte
44
+ // selector). Slot 0 = bytes [4..35], slot 1 = bytes [36..67], etc.
45
+ // ─────────────────────────────────────────────────────────────────────────────
46
+
47
+ library SailCalldata {
48
+ // ── Guards ────────────────────────────────────────────────────────────────
49
+
50
+ /// @notice Returns true when txData is long enough to hold `params` static
51
+ /// 32-byte ABI slots after the 4-byte selector.
52
+ /// @dev Call this once at the top of evaluate() before any slot access.
53
+ /// Returns false (not revert) so evaluate() can return false cleanly.
54
+ function hasParams(bytes calldata txData, uint256 params) internal pure returns (bool) {
55
+ return txData.length >= 4 + params * 32;
56
+ }
57
+
58
+ // ── Static-type extractors ────────────────────────────────────────────────
59
+ // All assume hasParams() has already been checked for the relevant slot.
60
+ // Accessing an out-of-bounds slot will cause the calldata slice to revert —
61
+ // always guard with hasParams() first.
62
+
63
+ /// @notice Extract an address from ABI slot `i` (0-indexed after selector).
64
+ /// Masks to 20 bytes, discarding the ABI zero-padding in the upper 12.
65
+ function asAddress(bytes calldata txData, uint256 i) internal pure returns (address) {
66
+ return address(uint160(uint256(bytes32(txData[4 + i * 32 : 4 + (i + 1) * 32]))));
67
+ }
68
+
69
+ /// @notice Extract a uint256 from ABI slot `i`.
70
+ function asUint256(bytes calldata txData, uint256 i) internal pure returns (uint256) {
71
+ return uint256(bytes32(txData[4 + i * 32 : 4 + (i + 1) * 32]));
72
+ }
73
+
74
+ /// @notice Extract an int256 from ABI slot `i`.
75
+ function asInt256(bytes calldata txData, uint256 i) internal pure returns (int256) {
76
+ return int256(uint256(bytes32(txData[4 + i * 32 : 4 + (i + 1) * 32])));
77
+ }
78
+
79
+ /// @notice Extract a bytes32 from ABI slot `i`.
80
+ function asBytes32(bytes calldata txData, uint256 i) internal pure returns (bytes32) {
81
+ return bytes32(txData[4 + i * 32 : 4 + (i + 1) * 32]);
82
+ }
83
+
84
+ /// @notice Extract a bool from ABI slot `i` (true when slot value is non-zero).
85
+ function asBool(bytes calldata txData, uint256 i) internal pure returns (bool) {
86
+ return asUint256(txData, i) != 0;
87
+ }
88
+
89
+ /// @notice Extract a uint128 from ABI slot `i` (lower 128 bits of the slot).
90
+ function asUint128(bytes calldata txData, uint256 i) internal pure returns (uint128) {
91
+ return uint128(asUint256(txData, i));
92
+ }
93
+
94
+ /// @notice Extract a uint64 from ABI slot `i`.
95
+ function asUint64(bytes calldata txData, uint256 i) internal pure returns (uint64) {
96
+ return uint64(asUint256(txData, i));
97
+ }
98
+
99
+ /// @notice Extract a uint32 from ABI slot `i`.
100
+ function asUint32(bytes calldata txData, uint256 i) internal pure returns (uint32) {
101
+ return uint32(asUint256(txData, i));
102
+ }
103
+
104
+ /// @notice Extract a uint24 from ABI slot `i` (e.g. Uniswap fee tier).
105
+ function asUint24(bytes calldata txData, uint256 i) internal pure returns (uint24) {
106
+ return uint24(asUint256(txData, i));
107
+ }
108
+
109
+ /// @notice Extract a uint16 from ABI slot `i` (e.g. Aave referral code).
110
+ function asUint16(bytes calldata txData, uint256 i) internal pure returns (uint16) {
111
+ return uint16(asUint256(txData, i));
112
+ }
113
+
114
+ /// @notice Extract bytes4 (a function selector) from ABI slot `i`.
115
+ function asBytes4(bytes calldata txData, uint256 i) internal pure returns (bytes4) {
116
+ return bytes4(asBytes32(txData, i));
117
+ }
118
+ }
@@ -1,10 +1,10 @@
1
- [profile.default]
2
- src = "."
3
- out = "out"
4
- libs = ["lib"]
5
- remappings = ["@sail/interfaces/=interfaces/"]
6
- solc = "0.8.26"
7
- optimizer = true
8
- optimizer_runs = 200
9
- # Examples compile with: forge build (from this directory)
10
- # Deploy within a Sailor project using: sailor mandate deploy --contract <Name> --args '[...]'
1
+ [profile.default]
2
+ src = "."
3
+ out = "out"
4
+ libs = ["lib"]
5
+ remappings = ["@sail/interfaces/=interfaces/"]
6
+ solc = "0.8.26"
7
+ optimizer = true
8
+ optimizer_runs = 200
9
+ # Examples compile with: forge build (from this directory)
10
+ # Deploy within a Sailor project using: sailor mandate deploy --contract <Name> --args '[...]'