Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 33 additions & 10 deletions contracts/contracts/ccip/router/contract.tolk
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,11 @@ fun updateOnRamps(mutate st: Storage, onRampUpdates: OnRamps?) {
var selectors = onRampUpdates.destChainSelectors.iter();
while (!selectors.empty()) {
val selector = selectors.next();
st.onRamps.set(selector, onRampUpdates.onRamp);
if (onRampUpdates.onRamp != null) {
st.onRamps.set(selector, onRampUpdates.onRamp);
} else {
st.onRamps.delete(selector);
}
}

emit(ON_RAMP_SET_TOPIC, OnRampSet {
Expand Down Expand Up @@ -336,7 +340,15 @@ fun onCCIPReceiveBounced(execId: ReceiveExecutorId, sender: address) {
fun onGetValidatedFee(msg: Router_GetValidatedFee<RemainingBitsAndRefs>, sender: address) {
val st = lazy Storage.load();
val ccipSend = lazy msg.ccipSend.load();
val onRamp = st.onRamps.mustGet(ccipSend.destChainSelector, Router_Error.DestChainNotEnabled as int);
val onRampResult = st.onRamps.get(ccipSend.destChainSelector);
if (!onRampResult.isFound) {
return sendMessageValidationFailed(sender, Router_Error.DestChainNotEnabled as uint256, msg.ccipSend, msg.context);
}

val onRamp = onRampResult.loadValue();
if (onRamp.isZero()) {
return sendMessageValidationFailed(sender, Router_Error.DestChainNotEnabled as uint256, msg.ccipSend, msg.context);
};

val feeQuoterMsg = createMessage({
bounce: true,
Expand Down Expand Up @@ -401,14 +413,18 @@ fun onMessageValidatedFromOnRamp(msg: OnRamp_MessageValidated<Router_GetValidate
// Router ->> SenderAcc: Router_MessageValidationFailed
fun onMessageValidationFailedFromOnRamp(msg: OnRamp_MessageValidationFailed<Router_GetValidatedFeeContext>) {
val userAddress = msg.context.routerContext;
sendMessageValidationFailed(userAddress, msg.error, msg.msg, msg.context.userContext);
}

fun sendMessageValidationFailed(userAddress: address, error: uint256, msg: Cell<Router_CCIPSend>, userContext: RemainingBitsOrRef<RemainingBitsAndRefs>) {
val replyMsg = createMessage({
bounce: true,
value: 0,
dest: userAddress,
body: Router_MessageValidationFailed {
error: msg.error,
msg: msg.msg,
context: msg.context.userContext,
error: error,
msg: msg,
context: userContext,
}
});
replyMsg.send(SEND_MODE_CARRY_ALL_REMAINING_MESSAGE_VALUE);
Expand All @@ -429,11 +445,14 @@ fun onCCIPSend(msg: Router_CCIPSend, sender: address, value: coins) {
msg.feeToken = st.wrappedNative
}

val onRamp = st.onRamps.mustGet(msg.destChainSelector, Router_Error.DestChainNotEnabled as int);
val onRampResult = st.onRamps.get(msg.destChainSelector);
if (!onRampResult.isFound || onRampResult.loadValue().isZero()) {
return sendMessageRejected(sender, msg.queryID, Router_Error.DestChainNotEnabled as uint256);
}
val sendMsg = createMessage({
bounce: true,
value: 0,
dest: onRamp,
dest: onRampResult.loadValue(),
body: OnRamp_Send { msg: msg.toCell(), metadata: Metadata{ sender, value } },
});
sendMsg.send(SEND_MODE_CARRY_ALL_REMAINING_MESSAGE_VALUE);
Expand Down Expand Up @@ -546,13 +565,17 @@ fun onSendACK(msg: Router_MessageSent) {
// OnRamp ->> Router: Router_MessageRejected
// Router ->> SenderAcc: Router_CCIPSendNACK
fun onSendNACK(msg: Router_MessageRejected) {
sendMessageRejected(msg.sender, msg.queryID, msg.error);
}

fun sendMessageRejected(sender: address, queryID: uint64, error: uint256) {
val nackMsg = createMessage({
bounce: true,
value: 0,
dest: msg.sender,
dest: sender,
body: Router_CCIPSendNACK {
queryID: msg.queryID,
error: msg.error,
queryID: queryID,
error: error,
}
});

Expand Down
2 changes: 1 addition & 1 deletion contracts/contracts/ccip/router/events.tolk
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import "../../lib/utils"
const ON_RAMP_SET_TOPIC: int = stringCrc32("OnRampSet");
struct OnRampSet {
destChainSelectors: SnakedCell<uint64>;
onRamp: address;
onRamp: address?;
}

const OFF_RAMP_ADDED_TOPIC: int = stringCrc32("OffRampAdded");
Expand Down
2 changes: 1 addition & 1 deletion contracts/contracts/ccip/router/types.tolk
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import "../../lib/utils"

struct OnRamps {
destChainSelectors: SnakedCell<uint64>;
onRamp: address;
onRamp: address?;
}

struct OffRamps {
Expand Down
4 changes: 2 additions & 2 deletions contracts/tests/Logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -290,10 +290,10 @@ export const testLogRampSet = (message: Message, from: Address, match: CCIPLogs.
return testLog(message, from, CCIPLogs.LogTypes.OnRampSet, (x) => {
const cs = x.beginParse()
const selectors = fromSnakeData(cs.loadRef(), (x) => x.loadUintBig(64))
const addr = cs.loadAddress()
const addr = cs.loadMaybeAddress()
const msg = {
destChainSelectors: selectors,
onRamp: addr,
onRamp: addr ?? undefined,
}
equalsObject(msg, match)
return true
Expand Down
60 changes: 57 additions & 3 deletions contracts/tests/ccip/router/Router.ccipSend.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
TEST_TOKEN_ADDR,
contractsCoverageConfig,
} from './Router.Setup'
import { ZERO_ADDRESS } from '../../../src/utils'

describe('Router', () => {
let blockchain: Blockchain
Expand Down Expand Up @@ -78,7 +79,7 @@ describe('Router', () => {
})
})

it('should reject message for disabled dest chain', async () => {
it('should reject message for disabled dest chain (missing OnRamp)', async () => {
const badMsg = { ...msg, destChainSelector: msg.destChainSelector + 1n }
const result = await router.sendCcipSend(sender.getSender(), {
value: toNano('1'),
Expand All @@ -88,8 +89,61 @@ describe('Router', () => {
expect(result.transactions).toHaveTransaction({
from: sender.address,
to: router.address,
success: false,
exitCode: rt.RouterError.DestChainNotEnabled,
success: true,
})

expect(result.transactions).toHaveTransaction({
from: router.address,
to: sender.address,
op: rt.opcodes.out.ccipSendNACK,
body(x) {
if (!x) return false
const decoded = rt.builder.message.out.ccipSendNACK.load(x.beginParse())
return decoded.error === BigInt(rt.RouterError.DestChainNotEnabled)
},
})
})

it('should reject message for disabled dest chain (zero address)', async () => {
// Disable the onRamp for the chain
{
const result = await router.sendApplyRampUpdatesSetRamps(deployer.getSender(), {
value: toNano('1'),
data: {
queryID: 1n,
onRamps: {
destChainSelectors: [CHAINSEL_EVM_TEST_90000001],
onRamp: undefined,
},
},
})
expect(result.transactions).toHaveTransaction({
from: deployer.address,
to: router.address,
success: true,
})
}

const result = await router.sendCcipSend(sender.getSender(), {
value: toNano('1'),
body: msg,
})

expect(result.transactions).toHaveTransaction({
from: sender.address,
to: router.address,
success: true,
})

expect(result.transactions).toHaveTransaction({
from: router.address,
to: sender.address,
op: rt.opcodes.out.ccipSendNACK,
body(x) {
if (!x) return false
const decoded = rt.builder.message.out.ccipSendNACK.load(x.beginParse())
return decoded.error === BigInt(rt.RouterError.DestChainNotEnabled)
},
})
})

Expand Down
78 changes: 75 additions & 3 deletions contracts/tests/ccip/router/Router.getFee.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
TEST_TOKEN_ADDR,
contractsCoverageConfig,
} from './Router.Setup'
import { ZERO_ADDRESS } from '../../../src/utils'

describe('Router', () => {
let blockchain: Blockchain
Expand Down Expand Up @@ -92,7 +93,7 @@ describe('Router', () => {
})
})

it('should reject getValidatedFee for disabled dest chain', async () => {
it('should reject getValidatedFee for disabled dest chain (missing OnRamp)', async () => {
const badMsg = {
queryID: 1,
destChainSelector: CHAINSEL_EVM_TEST_90000001 + 1n,
Expand All @@ -118,8 +119,79 @@ describe('Router', () => {
expect(result.transactions).toHaveTransaction({
from: sender.address,
to: router.address,
success: false,
exitCode: rt.RouterError.DestChainNotEnabled,
success: true,
})

expect(result.transactions).toHaveTransaction({
from: router.address,
to: sender.address,
op: rt.opcodes.out.messageValidationFailed,
body(x) {
if (!x) return false
const decoded = rt.builder.message.out.messageValidationFailed.load(x.beginParse())
return decoded.error === BigInt(rt.RouterError.DestChainNotEnabled)
},
})
})

it('should reject getValidatedFee for disabled dest chain (zero address)', async () => {
// Disable the onRamp for the chain
{
const result = await router.sendApplyRampUpdatesSetRamps(deployer.getSender(), {
value: toNano('1'),
data: {
queryID: 1n,
onRamps: {
destChainSelectors: [CHAINSEL_EVM_TEST_90000001],
onRamp: undefined,
},
},
})

expect(result.transactions).toHaveTransaction({
from: deployer.address,
to: router.address,
success: true,
})
}

const badMsg = {
queryID: 1,
destChainSelector: CHAINSEL_EVM_TEST_90000001,
receiver: EVM_ADDRESS,
data: Cell.EMPTY,
tokenAmounts: [],
feeToken: TEST_TOKEN_ADDR,
extraArgs: rt.builder.data.extraArgs
.encode({
kind: 'generic-v2',
gasLimit: 100n,
allowOutOfOrderExecution: true,
})
.asCell(),
}
const result = await router.sendGetValidatedFee(
sender.getSender(),
toNano('0.5'),
badMsg,
beginCell().asSlice(),
)

expect(result.transactions).toHaveTransaction({
from: sender.address,
to: router.address,
success: true,
})

expect(result.transactions).toHaveTransaction({
from: router.address,
to: sender.address,
op: rt.opcodes.out.messageValidationFailed,
body(x) {
if (!x) return false
const decoded = rt.builder.message.out.messageValidationFailed.load(x.beginParse())
return decoded.error === BigInt(rt.RouterError.DestChainNotEnabled)
},
})
})

Expand Down
2 changes: 1 addition & 1 deletion contracts/wrappers/ccip/Logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export type ReceiverCCIPMessageReceived = {

export type OnRampSet = {
destChainSelectors: bigint[]
onRamp: Address
onRamp?: Address
}

export type OffRampAdded = {
Expand Down
2 changes: 1 addition & 1 deletion contracts/wrappers/ccip/Router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,7 @@ export type ApplyRampUpdates = {

export type OnRamps = {
destChainSelectors: bigint[]
onRamp: Address
onRamp?: Address
}

export type OffRamps = {
Expand Down
5 changes: 4 additions & 1 deletion deployment/ccip/operation/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,10 @@ func applyRampUpdates(b operations.Bundle, deps config.CCIPDeps, in ApplyRampUpd
func updateRouterOnramps(routerAddr address.Address, onRampUpdates map[string][]router.ChainSelector) ([][]byte, error) {
msgs := make([]*tlb.InternalMessage, 0)
for onRampAddrStr, selectors := range onRampUpdates {
rampAddr := address.MustParseAddr(onRampAddrStr)
var rampAddr *address.Address
if onRampAddrStr != "" {
rampAddr = address.MustParseAddr(onRampAddrStr)
}
input := router.ApplyRampUpdates{
OnRampUpdates: &router.OnRamps{
DestChainSelectors: selectors,
Expand Down
Loading