Skip to content

fix receiver should use reserve and carry_all_balance#631

Open
vicentevieytes wants to merge 1 commit intomainfrom
vv/fix-receiver-draining
Open

fix receiver should use reserve and carry_all_balance#631
vicentevieytes wants to merge 1 commit intomainfrom
vv/fix-receiver-draining

Conversation

@vicentevieytes
Copy link
Collaborator

No description provided.

Copilot AI review requested due to automatic review settings February 26, 2026 23:39
@vicentevieytes vicentevieytes requested a review from a team as a code owner February 26, 2026 23:39
@github-actions
Copy link

👋 vicentevieytes, 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!

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates the CCIP test receiver contract to preserve its original TON balance when handling ccipReceive, aligning the confirmation-send behavior with the intended reserve + carry-all-balance pattern.

Changes:

  • Add a regression test asserting the receiver’s balance is unchanged after a successful ccipReceive.
  • Update the test receiver contract to reserve the original balance and send the confirmation with SEND_MODE_CARRY_ALL_BALANCE.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
contracts/tests/ccip/Receiver.spec.ts Adds a balance-preservation regression test for successful ccipReceive.
contracts/contracts/ccip/test/receiver/contract.tolk Reserves original balance and changes confirmation send mode to carry all balance.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 30 to +43
@@ -49,6 +38,20 @@ fun onInternalMessage(in: InMessage) {
processConsumeAllGas()
}
}

reserveToncoinsOnBalance(0, RESERVE_MODE_INCREASE_BY_ORIGINAL_BALANCE);
msg.validateAndConfirm({
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

processAccept / processRejectAll / processConsumeAllGas runs before validateAndConfirm (router + minValue checks). This changes semantics (e.g., unauthorized/low-value messages can now hit ConsumeAllGas and fail with out-of-gas instead of Unauthorized/LowValue) and can burn gas before failing. Validate the sender/value first (or split validation from confirmation), then run behavior, then reserve+send confirmation so failures are deterministic and cheap.

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@patricios-space hmm this is true, the minValue check should happen at the very beginning, but setting the action for the confirm message with this send mode and the reserve at the very beginning fails to preserve the original balance.

Copy link
Collaborator

@patricios-space patricios-space Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I was about to mention this. Then we should split validateAndConfirm, runing the validate before, and confirm after.

})
})

it('should keep original balance after succesfully receiving', async () => {
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in test name: "succesfully" should be "successfully".

Suggested change
it('should keep original balance after succesfully receiving', async () => {
it('should keep original balance after successfully receiving', async () => {

Copilot uses AI. Check for mistakes.
processConsumeAllGas()
}
}

Copy link
Collaborator Author

@vicentevieytes vicentevieytes Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to move this validateAndConfirm code block under the process functions. Turns out that if you do the reserve and send trick before the emit then it does not work, it has to be the last message sent. @patricios-space

Comment on lines 30 to +43
@@ -49,6 +38,20 @@ fun onInternalMessage(in: InMessage) {
processConsumeAllGas()
}
}

reserveToncoinsOnBalance(0, RESERVE_MODE_INCREASE_BY_ORIGINAL_BALANCE);
msg.validateAndConfirm({
Copy link
Collaborator

@patricios-space patricios-space Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I was about to mention this. Then we should split validateAndConfirm, runing the validate before, and confirm after.

Comment on lines +278 to +281

const finalBalance = (await blockchain.getContract(receiver.address)).balance
expect(finalBalance).toEqual(initialBalance)
})
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a flaky test because storage fee could be charged on message delivery. This will take it into consideration:

Suggested change
const finalBalance = (await blockchain.getContract(receiver.address)).balance
expect(finalBalance).toEqual(initialBalance)
})
const tx = result.transactions.find(
(tx) =>
tx.inMessage &&
tx.inMessage.info.src &&
tx.inMessage.info.src instanceof Address &&
tx.inMessage.info.src.equals(deployer.address) &&
tx.inMessage.info.dest &&
tx.inMessage.info.dest instanceof Address &&
tx.inMessage.info.dest.equals(receiver.address),
)
if (!tx || tx.description.type != 'generic') {
throw new Error('Expected an internal message')
}
const storageFees = tx.description.storagePhase?.storageFeesCollected || toNano('0')
const finalBalance = (await blockchain.getContract(receiver.address)).balance
expect(finalBalance).toEqual(initialBalance - storageFees)
})

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants