Skip to content

Commit c6cb811

Browse files
committed
Add ContextExecutor lib and contract
1 parent 220c46e commit c6cb811

10 files changed

Lines changed: 837 additions & 25 deletions

File tree

contracts/Acton.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ src = "contracts/ccip/ccipsend_executor/contract.tolk"
3838
domain = "ccip"
3939
depends = []
4040

41+
[contracts.ContextExecutor]
42+
display-name = "link.chain.ton.ccip.ContextExecutor"
43+
src = "contracts/ccip/executors/context_executor/contract.tolk"
44+
domain = "ccip"
45+
depends = []
46+
4147
[contracts.ReceiveExecutor]
4248
display-name = "link.chain.ton.ccip.ReceiveExecutor"
4349
src = "contracts/ccip/receive_executor/contract.tolk"
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Executors
2+
3+
CCIP per-message sharded contracts which manage the context and state of the on-ramp and off-ramp flows.
4+
5+
## TODO
6+
7+
- [ ] Move contracts/ccip/ccipsend_executor to this path
8+
- [ ] Move contracts/ccip/ccipreceive_executor to this path
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Context Executor
2+
3+
4+
5+
## Basic flow
6+
7+
1. On/OffRamp initializes the Executor
8+
2. Executor calls to sharded TP registry
9+
3. TP registry replies with TP address
10+
4. Executor uses On/OffRamp to proxy operations to TP (lockOrBurn/mintOrRelease)
11+
5. TP uses the Executor as a ContextExecutor to store contex for the current message, and forward messages from specific senders (accelerates the flow)
12+
13+
## Core operations
14+
15+
1. ContextExecutor_Init -> ContextExecutor_Reply
16+
2. ContextExecutor_Ask -> ContextExecutor_Reply
17+
3. InMessage from: context.toForward -> ContextExecutor_ForwardNotification
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// SPDX-License-Identifier: BUSL-1.1
2+
tolk 1.4.1
3+
4+
import "lib"
5+
import "messages"
6+
import "types"
7+
8+
contract ContextExecutor {
9+
author: "SmartContract Chainlink Limited SEZC"
10+
version: "0.1.0"
11+
description: "link.chain.ton.ccip.ContextExecutor"
12+
13+
// TODO: generic not supported here
14+
storage: ContextExecutor_Data<cell>
15+
incomingMessages: ContextExecutor_InMessage<cell>
16+
}
17+
18+
fun onInternalMessage(in: InMessage) {
19+
var executor = ContextExecutor<cell>.load();
20+
val handled = executor.onInternalMessage(ContextExecutor_InMessageForward {
21+
senderAddress: in.senderAddress,
22+
valueCoins: in.valueCoins,
23+
valueExtra: in.valueExtra,
24+
originalForwardFee: in.originalForwardFee,
25+
createdLt: in.createdLt,
26+
createdAt: in.createdAt,
27+
body: in.body.toCell(),
28+
});
29+
30+
if (handled) {
31+
executor.store();
32+
return;
33+
}
34+
35+
assert (in.body.isEmpty()) throw 0xFFFF;
36+
}
37+
38+
get fun typeAndVersion(): (slice, slice) {
39+
return ("link.chain.ton.ccip.ContextExecutor".literalSlice(), "0.1.0".literalSlice());
40+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// SPDX-License-Identifier: BUSL-1.1
2+
import "@stdlib/lisp-lists"
3+
import "../../../lib/utils"
4+
5+
import "types"
6+
import "messages"
7+
8+
struct ContextExecutor<T> {
9+
data: ContextExecutor_Data<T>;
10+
}
11+
12+
@inline
13+
fun ContextExecutor_Data<T>.fromContractData() {
14+
return ContextExecutor_Data<T>.fromCell(contract.getData());
15+
}
16+
17+
@inline
18+
fun ContextExecutor_Data<T>.storeAsContractData(self) {
19+
contract.setData(self.toCell());
20+
}
21+
22+
@inline
23+
fun ContextExecutor<T>.load(): ContextExecutor<T> {
24+
val data = ContextExecutor_Data<T>.fromContractData();
25+
return ContextExecutor<T> { data };
26+
}
27+
28+
fun ContextExecutor<T>.store(self) {
29+
self.data.storeAsContractData();
30+
}
31+
32+
fun ContextExecutor<T>.onInternalMessage(
33+
mutate self,
34+
forward: ContextExecutor_InMessageForward,
35+
): bool {
36+
val msg = lazy ContextExecutor_InMessage<T>.fromCell(forward.body);
37+
38+
match (msg) {
39+
ContextExecutor_Init<T> => {
40+
self.data.context = msg.context;
41+
self.data.forwardFrom = msg.forwardFrom;
42+
43+
val reply = createMessage({
44+
bounce: true,
45+
value: 0,
46+
dest: forward.senderAddress,
47+
body: ContextExecutor_Reply<T> {
48+
queryId: msg.queryId,
49+
id: self.data.id,
50+
context: self.data.context,
51+
forwardFrom: self.data.forwardFrom,
52+
forwardPayload: beginCell().storeUint(0, 64).endCell() as Cell<T>,
53+
},
54+
});
55+
reply.send(SEND_MODE_CARRY_ALL_REMAINING_MESSAGE_VALUE);
56+
57+
return true;
58+
}
59+
ContextExecutor_Ask => {
60+
val reply = createMessage({
61+
bounce: true,
62+
value: 0,
63+
dest: forward.senderAddress,
64+
body: ContextExecutor_Reply<T> {
65+
queryId: msg.queryId,
66+
id: self.data.id,
67+
context: self.data.context,
68+
forwardFrom: self.data.forwardFrom,
69+
forwardPayload: msg.forwardPayload,
70+
},
71+
});
72+
reply.send(SEND_MODE_CARRY_ALL_REMAINING_MESSAGE_VALUE);
73+
74+
return true;
75+
}
76+
else => {
77+
if (self._isForwardSender(forward.senderAddress)) {
78+
val reply = createMessage({
79+
bounce: false,
80+
value: 0,
81+
dest: forward.senderAddress,
82+
body: ContextExecutor_ForwardNotification<T> {
83+
id: self.data.id,
84+
context: self.data.context,
85+
forwardFrom: self.data.forwardFrom,
86+
message: forward.toCell(),
87+
},
88+
});
89+
reply.send(SEND_MODE_CARRY_ALL_REMAINING_MESSAGE_VALUE);
90+
91+
return true;
92+
}
93+
94+
return forward.body.beginParse().isEmpty();
95+
}
96+
}
97+
}
98+
99+
fun ContextExecutor<T>._isForwardSender(self, sender: address): bool {
100+
var iter = ArrayIterator<address>.new(self.data.forwardFrom);
101+
while (!iter.empty()) {
102+
if (iter.next() == sender) {
103+
return true;
104+
}
105+
}
106+
107+
return false;
108+
}
109+
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// SPDX-License-Identifier: BUSL-1.1
2+
import "types"
3+
4+
struct (0x3c50a300) ContextExecutor_Init<T> {
5+
queryId: uint64;
6+
context: Cell<T>;
7+
forwardFrom: array<address>;
8+
}
9+
10+
struct (0x3c50a301) ContextExecutor_Ask {
11+
queryId: uint64;
12+
13+
// Per request context
14+
forwardPayload: cell;
15+
}
16+
17+
type ContextExecutor_InMessage<T> =
18+
| ContextExecutor_Init<T>
19+
| ContextExecutor_Ask;
20+
21+
struct (0x3c50a302) ContextExecutor_Reply<T> {
22+
queryId: uint64;
23+
24+
id: uint64;
25+
context: Cell<T>;
26+
forwardFrom: array<address>;
27+
28+
// Per request context
29+
forwardPayload: cell;
30+
}
31+
32+
struct (0x3c50a303) ContextExecutor_ForwardNotification<T> {
33+
// Notice: no queryId here, because it's not a reply to a specific request
34+
35+
id: uint64;
36+
context: Cell<T>;
37+
forwardFrom: array<address>;
38+
39+
// Forwarded in-message
40+
message: Cell<ContextExecutor_InMessageForward>;
41+
}
42+
43+
type ContextExecutor_OutMessage<T> =
44+
| ContextExecutor_Reply<T>
45+
| ContextExecutor_ForwardNotification<T>;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// SPDX-License-Identifier: BUSL-1.1
2+
3+
struct ContextExecutor_Data<C> {
4+
id: uint64;
5+
owner: address;
6+
context: Cell<C>;
7+
forwardFrom: array<address>;
8+
}
9+
10+
struct ContextExecutor_InMessageForward {
11+
senderAddress: address // an internal address from which the message arrived
12+
valueCoins: coins // ton amount attached to an incoming message
13+
valueExtra: ExtraCurrenciesMap // extra currencies attached to an incoming message
14+
originalForwardFee: coins // fee that was paid by the sender
15+
createdLt: uint64 // logical time when a message was created
16+
createdAt: uint32 // unixtime when a message was created
17+
body: cell // message body, parse it with `lazy AllowedMsg.fromSlice(in.body)`
18+
}

contracts/contracts/ccip/pools/token_pool.tolk

Lines changed: 11 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,10 @@ fun TokenPool<T>.validateLockOrBurn(
666666
self.consumeOutboundRateLimit(request.remoteChainSelector, destTokenAmount);
667667
}
668668

669+
// TODO: use ContextExecutor as `replyTo` target for the preflight check
670+
// The CE will forward us the message and the context to continue the operation (async/await)
671+
672+
// TODO: we should not consume RL if the preflight check fails - if wait, skip consuming; consume on callback
669673
// TODO: preflightCheck needs to indicate if we can continue or wait for an async trigger (to continue)
670674
val wait = self.preflightCheck(request, requestedFinalityConfig, tokenArgs, destTokenAmount);
671675

@@ -734,6 +738,13 @@ fun TokenPool<T>.onLockOrBurnTransfer(
734738
self.context = self.hooks.onLockOrBurnTransfer(self.context!, sender, msg);
735739
}
736740

741+
// We either get this transfer from the Router, in which case we trust the transfer is valid;
742+
// Or for a different sender (e.g., per-user deposit account), we need to validate the transfer,
743+
// which we can only do with context -> recover from ContextExecutor.
744+
//
745+
// The context contains info about the sender, the amount, etc. that we can use to validate the transfer.
746+
// If we can't validate the transfer, we try to return the funds.
747+
737748
// TODO: can we generalize more (e.g., return of Jettons to sender on error)
738749
}
739750

@@ -769,31 +780,6 @@ fun TokenPool<T>.onLockOrBurnTransferFinalize(
769780
}
770781
}
771782

772-
// function lockOrBurn(
773-
// Pool.LockOrBurnInV1 calldata lockOrBurnIn,
774-
// bytes4 requestedFinalityConfig,
775-
// bytes calldata tokenArgs
776-
// ) public virtual returns (Pool.LockOrBurnOutV1 memory, uint256 destTokenAmount) {
777-
// uint256 feeAmount = _getFee(lockOrBurnIn, requestedFinalityConfig);
778-
// _validateLockOrBurn(lockOrBurnIn, requestedFinalityConfig, tokenArgs, feeAmount);
779-
// destTokenAmount = lockOrBurnIn.amount - feeAmount;
780-
// _lockOrBurn(lockOrBurnIn.remoteChainSelector, destTokenAmount);
781-
782-
// emit LockedOrBurned({
783-
// remoteChainSelector: lockOrBurnIn.remoteChainSelector,
784-
// token: lockOrBurnIn.localToken,
785-
// sender: msg.sender,
786-
// amount: destTokenAmount
787-
// });
788-
789-
// return (
790-
// Pool.LockOrBurnOutV1({
791-
// destTokenAddress: getRemoteToken(lockOrBurnIn.remoteChainSelector), destPoolData: _encodeLocalDecimals()
792-
// }),
793-
// destTokenAmount
794-
// );
795-
// }
796-
797783
fun TokenPool<T>.validateReleaseOrMint(
798784
mutate self,
799785
sender: address,
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { CompilerConfig } from '@ton/blueprint'
2+
3+
export const compile: CompilerConfig = {
4+
lang: 'tolk',
5+
entrypoint: 'contracts/ccip/executors/context_executor/contract.tolk',
6+
withStackComments: true,
7+
}

0 commit comments

Comments
 (0)