Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion .github/workflows/ccip-integration-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ jobs:
- name: Setup Environment and Run Tests
run: |
nix develop .#ccip-e2e -c scripts/e2e/setup-env.sh --core-dir "${GITHUB_WORKSPACE}/chainlink"
nix develop .#ccip-e2e -c scripts/e2e/run-test.sh --core-dir "${GITHUB_WORKSPACE}/chainlink" --test-command "${{ matrix.type.cmd }}"
nix develop .#ccip-e2e -c scripts/e2e/run-test.sh --core-dir "${GITHUB_WORKSPACE}/chainlink" --test-command "${{ matrix.type.cmd }}" --clean-logs

- name: Upload e2e test logs on success
if: success()
Expand Down
9 changes: 9 additions & 0 deletions .github/workflows/relayer-integration-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ jobs:
- name: Check out code
uses: actions/checkout@v5

- name: Relocate Nix store to /mnt
run: |
sudo mkdir -p /mnt/nix
sudo mkdir -p /nix
sudo mount --bind /mnt/nix /nix
df -h /nix /mnt

- name: Install Nix
uses: cachix/install-nix-action@02a151ada4993995686f9ed4f1be7cfbb229e56f # v31
with:
Expand All @@ -36,6 +43,8 @@ jobs:
run: |
pushd contracts
nix develop .#contracts -c yarn && yarn build
popd
./scripts/disk-cache-gc.sh

- name: Run integration tests
id: tests
Expand Down
5 changes: 3 additions & 2 deletions contracts/contracts/ccip/ccipsend_executor/contract.tolk
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import "../onramp/messages"
import "../../lib/utils"
import "../onramp/errors"
import "../router/messages"
import "../fee_quoter/types"

const CONTRACT_VERSION = "1.6.0";
const FACILITY_NAME = "com.chainlink.ton.ccip.CCIPSendExecutor";
Expand Down Expand Up @@ -89,15 +90,15 @@ fun getValidatedFee(feeQuoter: address, onrampSend: OnRamp_Send) {
}

fun CCIPSendExecutor<CCIPSendExecutor_State_OnGoingFeeValidation>.onMessageValidated(mutate self, msg: FeeQuoter_MessageValidated<RemainingBitsAndRefs>) {
if (msg.fee + Router_Costs.CCIPSend() > self.onrampSend.metadata.value) {
if (msg.fee.feeTokenAmount + Router_Costs.CCIPSend() > self.onrampSend.metadata.value) {
self.exitWithError(CCIPSendExecutor_Error.InsufficientFunds as int);
return;
}

self.exitSuccessfully(msg.fee);
}

fun CCIPSendExecutor<T>.exitSuccessfully(self, fee: coins) {
fun CCIPSendExecutor<T>.exitSuccessfully(self, fee: Fee) {
val sendMsg = createMessage({
bounce: true,
value: 0,
Expand Down
44 changes: 38 additions & 6 deletions contracts/contracts/ccip/fee_quoter/contract.tolk
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,10 @@ get fun validatedFeeCell(msg: Cell<Router_CCIPSend>): coins {
}

get fun validatedFee(msg: Router_CCIPSend): coins {
return calculateValidatedFee(msg).feeTokenAmount;
}

fun calculateValidatedFee(msg: Router_CCIPSend): Fee {
var st = lazy Storage.load();

val destChainConfigResult = st.destChainConfigs.get(msg.destChainSelector);
Expand All @@ -237,8 +241,9 @@ get fun validatedFee(msg: Router_CCIPSend): coins {

assert(destChainConfig.config.isEnabled, FeeQuoter_Error.DestChainNotEnabled);
assert(msg.feeToken != null, FeeQuoter_Error.FeeTokenNotSupported);
val premiumMultiplier = st.premiumMultiplierWeiPerEth.mustGet(msg.feeToken!, FeeQuoter_Error.FeeTokenNotSupported as int);
val feeTokenPrice = st.usdPerToken.mustGet(msg.feeToken, FeeQuoter_Error.FeeTokenNotSupported as int);
val feeToken = msg.feeToken!;
val premiumMultiplier = st.premiumMultiplierWeiPerEth.mustGet(feeToken, FeeQuoter_Error.FeeTokenNotSupported as int);
val feeTokenPrice = st.usdPerToken.mustGet(feeToken, FeeQuoter_Error.FeeTokenNotSupported as int);

val it = (msg.data as SnakedCell<uint8>).iter();
val (msgDataLen, errorCode) = it.countBytesSafe();
Expand Down Expand Up @@ -288,11 +293,35 @@ get fun validatedFee(msg: Router_CCIPSend): coins {
val premiumFee = mustProd(premiumFeeUsdWei, premiumMultiplier, FeeQuoter_Error.PremiumFeeOverflow as int);
val feeTokenValue = mustAdd(mustAdd(premiumFee, executionCost, FeeQuoter_Error.FeeCalculationOverflow as int), dataAvailabilityCost, FeeQuoter_Error.FeeCalculationOverflow as int);

// Check for division by zero (extremely small token prices)
// Check for division by zero (zero token prices)
assert(feeTokenPrice.value > 0, FeeQuoter_Error.TokenPriceTooLow);
val feeTokenAmount = (feeTokenValue / feeTokenPrice.value);

return mustCastToCoin(feeTokenAmount, FeeQuoter_Error.FeeOverflow as int);
// Equivalent to processMessageArgs in Solidity FeeQuoter
val feeValueJuels = calculateFeeValueJuels(st, feeToken, feeTokenValue, feeTokenAmount);

return Fee {
feeTokenAmount: mustCastToCoin(feeTokenAmount, FeeQuoter_Error.FeeOverflow as int),
feeValueJuels: feeValueJuels as uint96, // safe because already checked against maxFeeJuelsPerMsg
};
}

fun calculateFeeValueJuels(st: Storage, feeToken: address, feeTokenValue: coins, feeTokenAmount: coins): coins {
var feeValueJuels = feeTokenAmount;
if (feeToken != st.linkToken) {
val linkTokenPriceResult = st.usdPerToken.get(st.linkToken);
if (!linkTokenPriceResult.isFound) {
return 0;
}
val linkTokenPrice = linkTokenPriceResult.loadValue();
// Check for division by zero (zero token prices)
assert(linkTokenPrice.value > 0, FeeQuoter_Error.TokenPriceTooLow);
// convert from USD to juels
feeValueJuels = feeTokenValue / linkTokenPrice.value;
}

assert (feeValueJuels <= st.maxFeeJuelsPerMsg) throw FeeQuoter_Error.MessageFeeTooHigh;
return feeValueJuels;
}

fun _dataAvailabilityCost(
Expand Down Expand Up @@ -469,15 +498,18 @@ fun handleGetValidatedFee(payload: FeeQuoter_GetValidatedFee<RemainingBitsAndRef
val msg = payload.msg.load();
var response: OutMessage;
try {
val feeTokenAmount = validatedFee(msg);
val fee = calculateValidatedFee(msg);

response = createMessage({
bounce: true,
value: 0,
dest: sender,
body: FeeQuoter_MessageValidated<RemainingBitsAndRefs> {
fee: {
feeTokenAmount: fee.feeTokenAmount,
feeValueJuels: fee.feeValueJuels,
},
msg: payload.msg,
fee: feeTokenAmount,
context: payload.context,
}
});
Expand Down
3 changes: 2 additions & 1 deletion contracts/contracts/ccip/fee_quoter/errors.tolk
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ enum FeeQuoter_Error {
UnknownDestChainSelector
InsufficientFee
TokenTransfersNotSupported
UnauthorizedPriceUpdater
ExecutionCostOverflow // Unreachable
PremiumFeeOverflow // Unreachable
DataAvailabilityCostOverflow // Unreachable
FeeCalculationOverflow // Unreachable
TokenPriceTooLow
FeeOverflow
UnauthorizedPriceUpdater
MessageFeeTooHigh
}
2 changes: 1 addition & 1 deletion contracts/contracts/ccip/fee_quoter/messages.tolk
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ struct (0xD0984986) FeeQuoter_UpdateFeeTokens {
// --- Outgoing messages ---

struct (0x1fa60374) FeeQuoter_MessageValidated<T> {
fee: coins
fee: Fee,
msg: Cell<Router_CCIPSend>
context: T
}
Expand Down
4 changes: 4 additions & 0 deletions contracts/contracts/ccip/fee_quoter/types.tolk
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,7 @@ struct FeeQuoter_UpdateDestChainConfig {
destChainConfig: FeeQuoterDestChainConfig;
}

struct Fee {
feeTokenAmount: coins; // fee value in fee token
feeValueJuels: uint96; // fee value in juels
}
2 changes: 0 additions & 2 deletions contracts/contracts/ccip/offramp/contract.tolk
Original file line number Diff line number Diff line change
Expand Up @@ -823,8 +823,6 @@ get fun rmnRouter(): address {
return deployables.rmnRouter;
}

fun applySourceChainConfigUpdates() {}

/// Gets the current owner of the contract.
get fun owner(): address {
val st = lazy Storage.load();
Expand Down
9 changes: 5 additions & 4 deletions contracts/contracts/ccip/onramp/contract.tolk
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import "../../lib/versioning/upgradeable"
import "events"
import "../../deployable/types"
import "../fee_quoter/messages"
import "../fee_quoter/types"

const CONTRACT_VERSION = "1.6.0";
const RESERVE = ton("1");
Expand Down Expand Up @@ -171,8 +172,8 @@ fun onMessageValidated(msg: FeeQuoter_MessageValidated<OnRamp_GetValidatedFeeCon
value: 0,
dest: userAddress,
body: OnRamp_MessageValidated<RemainingBitsAndRefs> {
fee: msg.fee.feeTokenAmount,
msg: msg.msg,
fee: msg.fee,
context: userContext,
}
});
Expand Down Expand Up @@ -297,9 +298,9 @@ fun onExecutorFinishedSuccessfully(st: OnRamp_Storage, msg: OnRamp_ExecutorFinis
extraArgs: ccipsend.extraArgs,
tokenAmounts: ccipsend.tokenAmounts,
feeToken: ccipsend.feeToken!, // guaranteed to be non-null by this point
feeTokenAmount: msg.fee as uint256, // safe because it is 128 bits
feeTokenAmount: msg.fee.feeTokenAmount as uint256, // safe because it is 120 bits
}.toCell(),
feeValueJuels: 0, // TODO: this needs to be juels rather than TON?
feeValueJuels: msg.fee.feeValueJuels,
};

// Metadata hash preimage to ensure global uniqueness, ensuring 2 identical messages sent to 2 different lanes
Expand Down Expand Up @@ -327,7 +328,7 @@ fun onExecutorFinishedSuccessfully(st: OnRamp_Storage, msg: OnRamp_ExecutorFinis
sender: metadata.sender,
},
});
reserveToncoinsOnBalance(msg.fee, RESERVE_MODE_INCREASE_BY_ORIGINAL_BALANCE);
reserveToncoinsOnBalance(msg.fee.feeTokenAmount, RESERVE_MODE_INCREASE_BY_ORIGINAL_BALANCE);
confirmation.send(SEND_MODE_CARRY_ALL_BALANCE);
}

Expand Down
3 changes: 2 additions & 1 deletion contracts/contracts/ccip/onramp/messages.tolk
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import "../../lib/versioning/upgradeable"
import "../ccipsend_executor/messages"
import "../fee_quoter/messages"
import "../ccipsend_executor/types"
import "../fee_quoter/types"

type OnRamp_InMessage =
| OnRamp_Send
Expand Down Expand Up @@ -50,7 +51,7 @@ struct (0x10000003) OnRamp_SetDynamicConfig {

struct (0xCFA6B336) OnRamp_ExecutorFinishedSuccessfully {
executorID: CCIPSendExecutor_ID,
fee: coins
fee: Fee
msg: Cell<Router_CCIPSend>
metadata: Metadata
}
Expand Down
4 changes: 2 additions & 2 deletions contracts/contracts/lib/ocr/multi_ocr3_base.tolk
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ struct (0x2B78359F) OCR3Base_SetOCR3Config {
}

struct OCR3Base {
chainId: uint8;
commit: Cell<OCRConfig>?;
chainId: uint8;
commit: Cell<OCRConfig>?;
execute: Cell<OCRConfig>?;
}

Expand Down
Loading
Loading