Skip to content

Commit ecb6913

Browse files
authored
NONEVM-3014 - Unit test for error on manual execution before execution window (#341)
* fix bug * self review * self-review
1 parent f45f32d commit ecb6913

File tree

4 files changed

+105
-48
lines changed

4 files changed

+105
-48
lines changed

contracts/contracts/ccip/merkle_root/contract.tolk

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,10 @@ fun _initExecute(msg: MerkleRoot_Validate, sender: address) {
7373
val state = st.getMessageState(message.header.sequenceNumber);
7474
assert(state == ExecutionState.Failure || state == ExecutionState.Untouched, MerkleRoot_Error.SkippedAlreadyExecutedMessage);
7575

76-
val isManualExecute = msg.gasOverride != null;
76+
val isManualExecute = msg.gasOverride != null;
77+
7778
if (isManualExecute) {
7879
val isOldCommitReport = (blockchain.now() - st.timestamp > msg.permissionlessExecutionThresholdSeconds);
79-
8080
// Manual execution is fine if we previously failed or if the commit report is just too old.
8181
// (In that case the report will still be in UNTOUCHED state)
8282
assert(isOldCommitReport || state == ExecutionState.Failure, MerkleRoot_Error.ManualExecutionNotYetEnabled);
@@ -97,6 +97,7 @@ fun _initExecute(msg: MerkleRoot_Validate, sender: address) {
9797
root: st.root,
9898
metadataHash: msg.metadataHash,
9999
gasOverride: msg.gasOverride,
100+
executionState: state,
100101
}
101102
});
102103
executeValidated.send(SEND_MODE_REGULAR);

contracts/contracts/ccip/offramp/contract.tolk

Lines changed: 25 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import "../../lib/ocr/types"
1515
import "../../lib/utils.tolk"
1616
import "../fee_quoter/messages"
1717
import "../merkle_root/messages"
18-
import "../../lib/receiver/messages"
1918
import "../merkle_root/storage"
2019
import "../receive_executor/messages"
2120
import "../receive_executor/storage"
@@ -502,10 +501,9 @@ fun _updateCursedSubjects(msg: OffRamp_UpdateCursedSubjects, sender: address) {
502501
fun _executeValidated(msg: OffRamp_ExecuteValidated, sender: address) {
503502
val st = Storage.load();
504503

505-
// Validate the message comes from an owned MerkleRoot contract
506-
507504
val message = msg.message.load();
508505

506+
// Validate the message comes from an owned MerkleRoot contract
509507
assertSenderIsOwnedContract(st, sender, beginCell().storeUint(msg.root, MERKLE_ROOT_ID_SIZE));
510508

511509
emit(EXECUTION_STATE_CHANGED_TOPIC, ExecutionStateChanged {
@@ -528,44 +526,37 @@ fun _executeValidated(msg: OffRamp_ExecuteValidated, sender: address) {
528526
}
529527
};
530528

531-
if (msg.gasOverride == null) {
532-
deployAndInitializeExecutor(st, sender, message, executorAddress, execId);
533-
} else {
534-
//gasOverride indicates manual exec
535-
sendInitExecute(executorAddress, msg.gasOverride);
529+
// Check if this is the first time the exec flow is running given that in that case we need to
530+
// initialize the executor
531+
if (msg.executionState == ExecutionState.Untouched) {
532+
val deployMsg = createMessage({
533+
bounce: true,
534+
value: ton("0.5"), // TODO:
535+
dest: executorAddress,
536+
// TODO: toShard so it's collocated
537+
body: Deployable_Initialize {
538+
stateInit: {
539+
code: st.deployables.load().receiveExecutorCode,
540+
data: ReceiveExecutor_Storage {
541+
owner: contract.getAddress(),
542+
message: message.toCell(),
543+
root: sender,
544+
execId: execId,
545+
}.toCell(),
546+
}
547+
},
548+
});
549+
deployMsg.send(SEND_MODE_REGULAR);
536550
}
537-
}
538-
539-
fun deployAndInitializeExecutor(st: Storage, rootAddress: address, message: Any2TVMRampMessage, executorAddress: AutoDeployAddress, execId: ReceiveExecutorId) {
540-
val deployMsg = createMessage({
541-
bounce: true,
542-
value: ton("0.5"), // TODO:
543-
dest: executorAddress,
544-
// TODO: toShard so it's collocated
545-
body: Deployable_Initialize {
546-
stateInit: {
547-
code: st.deployables.load().receiveExecutorCode,
548-
data: ReceiveExecutor_Storage {
549-
owner: contract.getAddress(),
550-
message: message.toCell(),
551-
root: rootAddress,
552-
execId: execId,
553-
}.toCell(),
554-
}
555-
},
556-
});
557-
deployMsg.send(SEND_MODE_REGULAR);
558551

559-
sendInitExecute(executorAddress, null);
560-
}
561-
562-
fun sendInitExecute(executorAddress: AutoDeployAddress, gasOverride: coins?) {
552+
// Initiate the execution in the ReceiveExecutor. 'msg.gasOverride' may be null and that
553+
// means that is a manual exec flow
563554
val initExecMsg = createMessage ({
564555
bounce: true,
565556
value: ton("0.05"), // TODO:
566557
dest: executorAddress,
567558
body: ReceiveExecutor_InitExecute {
568-
gasOverride,
559+
gasOverride: msg.gasOverride,
569560
}
570561
});
571562
initExecMsg.send(SEND_MODE_REGULAR);

contracts/contracts/ccip/offramp/messages.tolk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ struct (0xc73d5a8a) OffRamp_ExecuteValidated {
4646
root: MerkleRootId;
4747
metadataHash: uint256;
4848
gasOverride: coins?;
49+
executionState: ExecutionState;
4950
}
5051

5152
//crc32('OffRamp_ManuallyExecute')

contracts/tests/ccip/OffRamp.spec.ts

Lines changed: 76 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,5 @@
11
import { Blockchain, SandboxContract, TreasuryContract } from '@ton/sandbox'
2-
import {
3-
Address,
4-
beginCell,
5-
Cell,
6-
contractAddress,
7-
Dictionary,
8-
fromNano,
9-
StateInit,
10-
toNano,
11-
} from '@ton/core'
2+
import { Address, beginCell, Cell, contractAddress, Dictionary, StateInit, toNano } from '@ton/core'
123
import { compile } from '@ton/blueprint'
134
import {
145
Any2TVMRampMessage,
@@ -26,7 +17,6 @@ import {
2617
SourceChainConfig,
2718
OffRamp,
2819
OffRampError,
29-
ReceiveExecutorError,
3020
} from '../../wrappers/ccip/OffRamp'
3121
import {
3222
MerkleRootError,
@@ -75,6 +65,7 @@ const CHAINSEL_TON = 13879075125137744094n
7565
const EVM_SENDER_ADDRESS_TEST = 0x1a5fdbc891c5d4e6ad68064ae45d43146d4f9f3an
7666
const EVM_ONRAMP_ADDRESS_TEST = 0x111111c891c5d4e6ad68064ae45d43146d4f9f3an
7767
const LEAF_DOMAIN_SEPARATOR = beginCell().storeUint(0, 256).asSlice()
68+
const PERMISSIONLESS_EXECUTION_THRESHOLD_SECONDS = 60
7869

7970
// These have to match the EVM states
8071
const EXECUTION_STATE_IN_PROGRESS = 1n
@@ -165,7 +156,7 @@ async function deployOffRampContract(
165156
feeQuoter: ZERO_ADDRESS,
166157
router: owner.address, // used to determine who can send RMN updates
167158
chainSelector: CHAINSEL_TON,
168-
permissionlessExecutionThresholdSeconds: 60,
159+
permissionlessExecutionThresholdSeconds: PERMISSIONLESS_EXECUTION_THRESHOLD_SECONDS,
169160
latestPriceSequenceNumber: 0n,
170161
}
171162

@@ -1416,6 +1407,79 @@ describe('OffRamp - Unit Tests', () => {
14161407
})
14171408
})
14181409

1410+
it('Manual execute after permissionlessExecutionThresholdSeconds', async () => {
1411+
const message = createTestMessage(1n, 1n, receiver.address) // empty data (Cell.EMPTY)
1412+
await setupAndCommitMessage(message)
1413+
const report = createExecuteReport([message])
1414+
1415+
// Try manual exec when is not enabled
1416+
const manualExecFirstAttempt = await manualExecuteReport(report)
1417+
expect(manualExecFirstAttempt.transactions).toHaveTransaction({
1418+
from: offRamp.address,
1419+
success: false,
1420+
exitCode: MerkleRootError.ManualExecutionNotYetEnabled,
1421+
})
1422+
1423+
// Almost there, still needs to fail
1424+
warpTime(PERMISSIONLESS_EXECUTION_THRESHOLD_SECONDS)
1425+
1426+
const manualExecSecondAttempt = await manualExecuteReport(report)
1427+
expect(manualExecSecondAttempt.transactions).toHaveTransaction({
1428+
from: offRamp.address,
1429+
success: false,
1430+
exitCode: MerkleRootError.ManualExecutionNotYetEnabled,
1431+
})
1432+
1433+
// One more sec and we are ready to go
1434+
warpTime(1)
1435+
1436+
const manualExecThirdAttempt = await manualExecuteReport(report, undefined, true)
1437+
expect(manualExecThirdAttempt.transactions).toHaveTransaction({
1438+
from: router.address,
1439+
to: receiver.address,
1440+
value: message.gasLimit,
1441+
success: true,
1442+
})
1443+
1444+
assertLog(
1445+
manualExecThirdAttempt.transactions,
1446+
offRamp.address,
1447+
CCIPLogs.LogTypes.ExecutionStateChanged,
1448+
{
1449+
sourceChainSelector: CHAINSEL_EVM_TEST_90000001,
1450+
sequenceNumber: 1n,
1451+
messageId: 1n,
1452+
state: EXECUTION_STATE_IN_PROGRESS,
1453+
},
1454+
)
1455+
1456+
assertLog(
1457+
manualExecThirdAttempt.transactions,
1458+
offRamp.address,
1459+
CCIPLogs.LogTypes.ExecutionStateChanged,
1460+
{
1461+
sourceChainSelector: CHAINSEL_EVM_TEST_90000001,
1462+
sequenceNumber: 1n,
1463+
messageId: 1n,
1464+
state: EXECUTION_STATE_SUCCESS,
1465+
},
1466+
)
1467+
1468+
assertLog(
1469+
manualExecThirdAttempt.transactions,
1470+
receiver.address,
1471+
CCIPLogs.LogTypes.ReceiverCCIPMessageReceived,
1472+
{
1473+
message: {
1474+
messageId: message.header.messageId,
1475+
sourceChainSelector: CHAINSEL_EVM_TEST_90000001,
1476+
sender: message.sender,
1477+
data: message.data,
1478+
},
1479+
},
1480+
)
1481+
})
1482+
14191483
it('Manual execute: receiver fails, then succeeds', async () => {
14201484
const message = createTestMessage(1n, 1n, receiver.address) // empty data (Cell.EMPTY)
14211485
await setupAndCommitMessage(message)

0 commit comments

Comments
 (0)