Token pools#756
Conversation
|
👋 krebernisak, thanks for creating this pull request! To help reviewers, please consider creating future PRs as drafts first. This allows you to self-review and make any final changes before notifying the team. Once you're ready, you can mark it as "Ready for review" to request feedback. Thanks! |
There was a problem hiding this comment.
Pull request overview
Adds CCIP “token pool” support by introducing a shared TokenPool core (rate limiting, chain/ramp config, cursed subjects) plus two concrete pool contracts (Lock/Release and Burn/Mint), along with TypeScript wrappers and sandbox tests.
Changes:
- Introduces new Tolk contracts for
TokenPool,LockReleaseTokenPool, andBurnMintTokenPool, including messages/events/types/rate-limiter logic. - Adds CCIP CCT Jetton contracts (minter/wallet) and TS wrappers for deployment and interactions.
- Adds TS wrapper utilities and new sandbox test suites for both pool variants.
Reviewed changes
Copilot reviewed 38 out of 38 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| contracts/wrappers/gen/ccip/pools/TokenPool.ts | Auto-generated TypeScript wrapper for TokenPool contract types/messages. |
| contracts/wrappers/ccip/TokenPool.ts | Handwritten TS codec/opcodes and helper senders for pool messages. |
| contracts/wrappers/ccip/LockReleaseTokenPool.ts | TS wrapper for LockReleaseTokenPool deployment, sends, and get-methods. |
| contracts/wrappers/ccip/BurnMintTokenPool.ts | TS wrapper for BurnMintTokenPool deployment, sends, and get-methods. |
| contracts/wrappers/ccip/CCTJettonMinter.ts | TS wrapper for CCIP CCT Jetton minter (deploy/mint/admin/wallet addr). |
| contracts/wrappers/ccip/CCTJettonCode.ts | Helper functions to load compiled CCT Jetton minter/wallet code. |
| contracts/wrappers/ccip.pools.LockReleaseTokenPool.compile.ts | Blueprint compile config for LockReleaseTokenPool Tolk entrypoint. |
| contracts/wrappers/ccip.pools.BurnMintTokenPool.compile.ts | Blueprint compile config for BurnMintTokenPool Tolk entrypoint. |
| contracts/wrappers/ccip.cct.JettonWallet.compile.ts | Blueprint compile config for CCT JettonWallet Tolk entrypoint. |
| contracts/wrappers/ccip.cct.JettonMinter.compile.ts | Blueprint compile config for CCT JettonMinter Tolk entrypoint. |
| contracts/tests/ccip/pools/TokenPool.behavior.ts | Shared behavioral test suite for token-pool ramp/curse/chain behaviors. |
| contracts/tests/ccip/pools/LockReleaseTokenPool.spec.ts | LockReleaseTokenPool integration tests using Jettons and behavior suite. |
| contracts/tests/ccip/pools/BurnMintTokenPool.spec.ts | BurnMintTokenPool integration tests using CCT jettons and behavior suite. |
| contracts/contracts/ccip/pools/types.tolk | TokenPool-related structs/constants (configs, fee config, v1 IO types). |
| contracts/contracts/ccip/pools/messages.tolk | Opcode-tagged internal message types for TokenPool operations. |
| contracts/contracts/ccip/pools/events.tolk | Shared TokenPool event structs/topics. |
| contracts/contracts/ccip/pools/errors.tolk | TokenPool error codes/enum. |
| contracts/contracts/ccip/pools/rate_limiter.tolk | Rate limiter config/bucket structs and refill/consume logic. |
| contracts/contracts/ccip/pools/token_pool.tolk | Core TokenPool logic: chain config, ramps, cursed subjects, rate limiting, fees. |
| contracts/contracts/ccip/pools/token_pool_contract.tolk | TokenPool contract template entrypoint for bindings (non-deployable stub). |
| contracts/contracts/ccip/pools/lock_release_token_pool/types.tolk | LockReleaseTokenPool-specific constants and pending-release structs. |
| contracts/contracts/ccip/pools/lock_release_token_pool/storage.tolk | LockReleaseTokenPool storage layout and initialization. |
| contracts/contracts/ccip/pools/lock_release_token_pool/messages.tolk | LockReleaseTokenPool custom inbound/bounced message types. |
| contracts/contracts/ccip/pools/lock_release_token_pool/events.tolk | LockReleaseTokenPool-specific event structs/topics. |
| contracts/contracts/ccip/pools/lock_release_token_pool/errors.tolk | LockReleaseTokenPool-specific error enum/facility identifiers. |
| contracts/contracts/ccip/pools/lock_release_token_pool/contract.tolk | LockReleaseTokenPool contract implementation (lock via transfer notify, release via off-ramp). |
| contracts/contracts/ccip/pools/burn_mint_token_pool/types.tolk | BurnMintTokenPool-specific constants and pending burn/mint structs. |
| contracts/contracts/ccip/pools/burn_mint_token_pool/storage.tolk | BurnMintTokenPool storage layout and initialization. |
| contracts/contracts/ccip/pools/burn_mint_token_pool/messages.tolk | BurnMintTokenPool custom inbound message types (incl. claim-minter-admin). |
| contracts/contracts/ccip/pools/burn_mint_token_pool/errors.tolk | BurnMintTokenPool-specific error enum/facility identifiers. |
| contracts/contracts/ccip/pools/burn_mint_token_pool/contract.tolk | BurnMintTokenPool contract implementation (burn on lock, mint on release). |
| contracts/contracts/ccip/fee_quoter/types.tolk | Removes duplicated constant now defined under pools types. |
| contracts/contracts/ccip/cct/JettonWallet.tolk | CCT jetton wallet contract implementation (receive/transfer/burn + bounce handling). |
| contracts/contracts/ccip/cct/JettonMinter.tolk | CCT jetton minter contract implementation (mint/admin lifecycle + wallet address). |
| contracts/contracts/ccip/cct/fees-management.tolk | Fee/gas/storage estimation utilities for CCT jetton contracts. |
| contracts/Acton.toml | Registers new pool contracts (and TokenPool template) in Acton config. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| export async function sendUpdateCursedSubjects( | ||
| provider: ContractProvider, | ||
| via: Sender, | ||
| value: bigint, | ||
| cursedSubjects: bigint[], | ||
| ) { | ||
| const dict = Dictionary.empty(Dictionary.Keys.BigInt(128), Dictionary.Values.Bool()) | ||
| cursedSubjects.forEach((subject) => dict.set(subject, true)) | ||
| await provider.internal(via, { | ||
| value, | ||
| sendMode: SendMode.PAY_GAS_SEPARATELY, | ||
| body: beginCell().storeUint(opcodes.in.updateCursedSubjects, 32).storeDict(dict).endCell(), | ||
| }) |
|
|
||
| RateLimiter_refillBucket(mutate bucket); | ||
| // TODO: should be RateLimiter error | ||
| // assert(bucket.tokens >= amount, TokenPool_Error.RateLimitExceeded); | ||
| bucket.tokens -= amount; | ||
| } |
| remoteChainSelector: request.remoteChainSelector, | ||
| details: TokenPool_LockedOrBurnedDetails { | ||
| token: request.localToken, | ||
| sender: transferInitiator, | ||
| amount: prepared.destTokenAmount, |
| runTokenPoolBehaviorTests('LockReleaseTokenPool', async () => ({ | ||
| pool: lockReleasePool, | ||
| deployer, | ||
| offRamp, | ||
| altOffRamp: deployer, | ||
| unauthorized: recipient, | ||
| recipient, | ||
| remoteChainSelector, | ||
| unsupportedChainSelector: remoteChainSelector + 1n, | ||
| unknownSourcePoolAddress: poolCodec.crossChainAddressFromBuffer(Buffer.from('unknown-source-pool')), | ||
| remoteTokenAddress: destTokenAddress, | ||
| onRampAddress: jettonSender.address, | ||
| destTokenAddress, | ||
| sourcePoolAddress, | ||
| localToken: jettonMinter.address, | ||
| })) |
| runTokenPoolBehaviorTests('BurnMintTokenPool', async () => ({ | ||
| pool: burnMintPool, | ||
| deployer, | ||
| offRamp, | ||
| altOffRamp: deployer, | ||
| unauthorized, | ||
| recipient, | ||
| remoteChainSelector, | ||
| unsupportedChainSelector: remoteChainSelector + 1n, | ||
| unknownSourcePoolAddress: poolCodec.crossChainAddressFromBuffer(Buffer.from('unknown-source-pool')), | ||
| remoteTokenAddress: destTokenAddress, | ||
| onRampAddress: deployer.address, | ||
| destTokenAddress, | ||
| sourcePoolAddress, | ||
| localToken: cctMinter.address, | ||
| })) |
| const GAS_CONSUMPTION_BurnNotification = 3855 | ||
|
|
||
|
|
||
| fun calculateJettonWalletMinStorageFee() { |
There was a problem hiding this comment.
You can use @stdlib/reflection here:
| fun calculateJettonWalletMinStorageFee() { | |
| val (minBits, maxBits, minRefs, maxRefs) =reflect.estimateSerializationOf<WalletStorage>(); |
See docs
4124e19 to
c53ea4d
Compare
4c98bef to
f6a86fb
Compare
f6a86fb to
5dd214c
Compare
3bab02d to
2de43cd
Compare
| // We either get this transfer from the Router, in which case we trust the transfer is valid; | ||
| // Or for a different sender (e.g., per-user deposit account), we need to validate the transfer, | ||
| // which we can only do with context -> recover from ContextExecutor. | ||
| // | ||
| // The context contains info about the sender, the amount, etc. that we can use to validate the transfer. | ||
| // If we can't validate the transfer, we try to return the funds. | ||
|
|
There was a problem hiding this comment.
So we have these two options:
Transfer comes from Router Wallet, and it's address is configured in the Pool:
- SendExecutor initializes LockOrBurn: SendExecutor -> OnRamp -> Router --[LockOrBurn]-> TokenPool
- TokenPool initiates withdraw: TokenPool -> SendExecutor -> OnRamp -> Router
- Router withdraws from user deposit account and sends it to the pool: Router -> UserAccount -> UserAccountWallet -> RouterWallet -> Router -> RouterWallet -> TokenPool Wallet -> TokenPool
Transfer comes from user account:
- SendExecutor initializes LockOrBurn: SendExecutor -> OnRamp -> Router --[LockOrBurn]-> TokenPool
- TokenPool initiates withdraw: TokenPool -> SendExecutor -> OnRamp -> Router
- Router indicates the user account to transfer to the pool directly: Router -> UserAccount -> UserAccountWallet -> TokenPool
- Token pool verifies the transfer comes from the user wallet
The second option is simpler when it comes to how many messages and handlers we need, but we need the context_executor or some similar way to store the account wallet address. How far away are we from having the executor deployments/access wired in here?
There was a problem hiding this comment.
Yes, that is basically it.
How far away are we from having the executor deployments/access wired in here?
The contract is roughly implemented, just need to start deploying/using it. Probably ready by end of next week, focusing on getting Lockbox + LockReleaseLockboxPool ready, then connect the executor.
There was a problem hiding this comment.
Sounds good 👍
Probably a good idea to leave that integration for a follow up PR
|
|
||
| CCIP per-message sharded contracts which manage the context and state of the on-ramp and off-ramp flows. | ||
|
|
||
| ## TODO |
There was a problem hiding this comment.
We should move this documentation to the docs directory
| val handled = executor.onInternalMessage(ContextExecutor_InMessageForward { | ||
| senderAddress: in.senderAddress, | ||
| valueCoins: in.valueCoins, | ||
| valueExtra: in.valueExtra, | ||
| originalForwardFee: in.originalForwardFee, | ||
| createdLt: in.createdLt, | ||
| createdAt: in.createdAt, | ||
| body: in.body.toCell(), | ||
| }); |
There was a problem hiding this comment.
Why not just pass InMessage?
There was a problem hiding this comment.
I see, InMessage cannot be serialized:
contracts/ccip/executors/context_executor/contract.tolk:16:23: error: `outgoingMessages` can not be serialized: type `ContextExecutor_OutMessage<cell>`
because alias `ContextExecutor_OutMessage<cell>` expands to `ContextExecutor_Reply<cell> | ContextExecutor_ForwardNotification<cell>`
because variant #2 of type `ContextExecutor_ForwardNotification<cell>` can't be serialized
because field `ContextExecutor_ForwardNotification<cell>.message` of type `Cell<InMessage>` can't be serialized
because field `InMessage.body` of type `slice` can't be serialized
because type `slice` can not be used for reading, it doesn't define binary width
hint: replace `slice` with `address` if it's an address, actually
hint: replace `slice` with `bits128` and similar if it represents fixed-width data without refs
16 | outgoingMessages: ContextExecutor_OutMessage<cell>
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
No description provided.