Skip to content
27 changes: 27 additions & 0 deletions bindings/bind/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ func compilePackageInternal(packageName contracts.Package, namedAddresses map[st
filepath.Join(dstRoot, "ccip", "ccip_offramp"),
filepath.Join(dstRoot, "ccip", "ccip_burn_mint_token"),
filepath.Join(dstRoot, "ccip", "ccip_dummy_receiver"),
filepath.Join(dstRoot, "ccip", "ccip_broken_receiver"),
filepath.Join(dstRoot, "ccip", "managed_token"),
filepath.Join(dstRoot, "ccip", "managed_token_faucet"),
filepath.Join(dstRoot, "ccip", "mock_eth_token"),
Expand Down Expand Up @@ -505,6 +506,32 @@ func compilePackageInternal(packageName contracts.Package, namedAddresses map[st
}
}

if packageName == contracts.CCIPBrokenReceiver {
mcmsAddr := namedAddresses["mcms"]
if !isZeroAddress(mcmsAddr) {
mcmsDir := filepath.Join(dstRoot, "mcms", "mcms")
if err := managePackage(mcmsDir, 1, rpcURL, env, mcmsAddr, mcmsAddr, pubfilePath); err != nil {
return PackageArtifact{}, fmt.Errorf("failed to manage MCMS dependency: %w", err)
}
} else {
fmt.Println("Skipping manage-package for MCMS (no published address found)")
}

ccipAddr := namedAddresses["ccip"]
if !isZeroAddress(ccipAddr) {
ccipDir := filepath.Join(dstRoot, "ccip", "ccip")
ccipOrigID := ccipAddr
if orig := namedAddresses["original_ccip_pkg"]; !isZeroAddress(orig) {
ccipOrigID = orig
}
if err := managePackage(ccipDir, 1, rpcURL, env, ccipOrigID, ccipAddr, pubfilePath); err != nil {
return PackageArtifact{}, fmt.Errorf("failed to manage CCIP dependency: %w", err)
}
} else {
fmt.Println("Skipping manage-package for CCIP (no published address found)")
}
}

if packageName == contracts.CCIPRouter {
mcmsAddr := namedAddresses["mcms"]
if !isZeroAddress(mcmsAddr) {
Expand Down

Large diffs are not rendered by default.

443 changes: 442 additions & 1 deletion bindings/generated/ccip/ccip/rmn_remote/rmn_remote.go

Large diffs are not rendered by default.

336 changes: 335 additions & 1 deletion bindings/generated/ccip/ccip_offramp/offramp/offramp.go

Large diffs are not rendered by default.

74 changes: 74 additions & 0 deletions contracts/ccip/ccip/sources/client.move
Original file line number Diff line number Diff line change
Expand Up @@ -163,3 +163,77 @@ public fun get_amount(input: &Any2SuiTokenAmount): u256 {
public fun get_token_and_amount(input: &Any2SuiTokenAmount): (address, u256) {
(input.token, input.amount)
}

// ================================================================
// | V2 Types |
// ================================================================

public struct Any2SuiMessageV2 {
message_id: vector<u8>,
source_chain_selector: u64,
sender: vector<u8>,
data: vector<u8>,
message_receiver: address,
token_receiver: address,
receiver_object_ids: vector<address>,
dest_token_amounts: vector<Any2SuiTokenAmount>,
}

public(package) fun new_any2sui_message_v2(
message_id: vector<u8>,
source_chain_selector: u64,
sender: vector<u8>,
data: vector<u8>,
message_receiver: address,
token_receiver: address,
receiver_object_ids: vector<address>,
dest_token_amounts: vector<Any2SuiTokenAmount>,
): Any2SuiMessageV2 {
Any2SuiMessageV2 {
message_id,
source_chain_selector,
sender,
data,
message_receiver,
token_receiver,
receiver_object_ids,
dest_token_amounts,
}
}

public(package) fun consume_any2sui_message_v2(
message: Any2SuiMessageV2,
receiver_package_id: address,
): (vector<u8>, u64, vector<u8>, vector<u8>, address, address, vector<address>, vector<Any2SuiTokenAmount>) {
let Any2SuiMessageV2 {
message_id,
source_chain_selector,
sender,
data,
message_receiver,
token_receiver,
receiver_object_ids,
dest_token_amounts,
} = message;
assert!(message_receiver == receiver_package_id, EMessageReceiverMismatch);

(
message_id,
source_chain_selector,
sender,
data,
message_receiver,
token_receiver,
receiver_object_ids,
dest_token_amounts,
)
}

public fun get_receiver_object_ids(message: &Any2SuiMessageV2): &vector<address> {
&message.receiver_object_ids
}

public fun assert_receiver_object<T: key>(message: &Any2SuiMessageV2, index: u64, object: &T) {
let expected_id = message.receiver_object_ids[index];
assert!(object::id_address(object) == expected_id, EMessageReceiverMismatch);
}
218 changes: 218 additions & 0 deletions contracts/ccip/ccip/sources/offramp_state_helper.move
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,196 @@ public fun deconstruct_receiver_params(_: &DestTransferCap, receiver_params: Rec
message_op.destroy_none();
}

// ================================================================
// | V2 Types and Functions |
// ================================================================

const EReceiverObjectMismatch: u64 = 11;

public struct ReceiverParamsV2 {
token_transfer: Option<DestTokenTransfer>,
message: Option<client::Any2SuiMessageV2>,
source_chain_selector: u64,
receipt: Option<CompletedDestTokenTransfer>,
}

public fun new_any2sui_message_v2(
_: &DestTransferCap,
message_id: vector<u8>,
source_chain_selector: u64,
sender: vector<u8>,
data: vector<u8>,
message_receiver: address,
token_receiver: address,
receiver_object_ids: vector<address>,
token_addresses: vector<address>,
token_amounts: vector<u256>,
): client::Any2SuiMessageV2 {
client::new_any2sui_message_v2(
message_id,
source_chain_selector,
sender,
data,
message_receiver,
token_receiver,
receiver_object_ids,
client::new_dest_token_amounts(token_addresses, token_amounts),
)
}

public fun create_receiver_params_v2(_: &DestTransferCap, source_chain_selector: u64): ReceiverParamsV2 {
ReceiverParamsV2 {
token_transfer: option::none(),
message: option::none(),
source_chain_selector,
receipt: option::none(),
}
}

public fun add_dest_token_transfer_v2(
_: &DestTransferCap,
receiver_params: &mut ReceiverParamsV2,
token_receiver: address,
remote_chain_selector: u64,
source_amount: u256,
dest_token_address: address,
dest_token_pool_package_id: address,
source_pool_address: vector<u8>,
source_pool_data: vector<u8>,
offchain_data: vector<u8>,
) {
assert!(receiver_params.token_transfer.is_none(), ETokenTransferAlreadyExists);

receiver_params
.token_transfer
.fill(DestTokenTransfer {
token_receiver,
remote_chain_selector,
source_amount,
dest_token_address,
dest_token_pool_package_id,
source_pool_address,
source_pool_data,
offchain_token_data: offchain_data,
});
}

public fun populate_message_v2(
_: &DestTransferCap,
receiver_params: &mut ReceiverParamsV2,
any2sui_message: client::Any2SuiMessageV2,
) {
assert!(receiver_params.message.is_none(), EMessageAlreadyExists);
receiver_params.message.fill(any2sui_message);
}

/// Extracts the V2 message from ReceiverParams. Enforces that the declared `used_object_ids`
/// match the source-committed `receiver_object_ids` in the message. This is the protocol-level
/// object binding enforcement point.
public fun extract_any2sui_message_v2(
receiver_params: &mut ReceiverParamsV2,
used_object_ids: vector<address>,
): client::Any2SuiMessageV2 {
assert!(receiver_params.message.is_some(), ENoMessageToExtract);
let message = receiver_params.message.extract();
assert!(used_object_ids == *client::get_receiver_object_ids(&message), EReceiverObjectMismatch);
message
}

public fun consume_any2sui_message_v2<TypeProof: drop>(
ref: &CCIPObjectRef,
message: client::Any2SuiMessageV2,
_: TypeProof,
): (vector<u8>, u64, vector<u8>, vector<u8>, address, address, vector<address>, vector<client::Any2SuiTokenAmount>) {
let proof_tn = type_name::with_defining_ids<TypeProof>();
let address_str = type_name::address_string(&proof_tn);
let receiver_package_id = address::from_ascii_bytes(&ascii::into_bytes(address_str));

let receiver_config = receiver_registry::get_receiver_config(ref, receiver_package_id);
let (_, proof_typename) = receiver_registry::get_receiver_config_fields(receiver_config);
assert!(proof_typename == proof_tn.into_string(), ETypeProofMismatch);

client::consume_any2sui_message_v2(message, receiver_package_id)
}

public fun get_dest_token_transfer_data_v2(
receiver_params: &ReceiverParamsV2,
): (address, u64, u256, address, address, vector<u8>, vector<u8>, vector<u8>) {
assert!(receiver_params.token_transfer.is_some(), ETokenTransferDoesNotExist);

let token_transfer = receiver_params.token_transfer.borrow();
(
token_transfer.token_receiver,
token_transfer.remote_chain_selector,
token_transfer.source_amount,
token_transfer.dest_token_address,
token_transfer.dest_token_pool_package_id,
token_transfer.source_pool_address,
token_transfer.source_pool_data,
token_transfer.offchain_token_data,
)
}

public fun complete_token_transfer_v2<TypeProof: drop>(
ref: &CCIPObjectRef,
receiver_params: &mut ReceiverParamsV2,
_: TypeProof,
) {
let dest_token_transfer = receiver_params.token_transfer.borrow();
let token_receiver = dest_token_transfer.token_receiver;
let dest_token_address = dest_token_transfer.dest_token_address;
let (_, _, _, _, _, type_proof, _, _) = registry::get_token_config_data(
ref,
dest_token_address,
);

let proof_tn = type_name::with_defining_ids<TypeProof>();
let proof_tn_str = type_name::into_string(proof_tn);
assert!(type_proof == proof_tn_str, ETypeProofMismatch);

let receipt = CompletedDestTokenTransfer {
token_receiver,
dest_token_address,
};

assert!(receiver_params.receipt.is_none(), ETokenTransferAlreadyCompleted);
receiver_params.receipt.fill(receipt);
}

public fun deconstruct_receiver_params_v2(_: &DestTransferCap, receiver_params: ReceiverParamsV2) {
let ReceiverParamsV2 {
token_transfer: mut token_transfer_op,
message: message_op,
source_chain_selector: _,
receipt: mut receipt_op,
} = receiver_params;

assert!(
token_transfer_op.is_none() && receipt_op.is_none() || (token_transfer_op.is_some() && receipt_op.is_some()),
EWrongReceiptAndTokenTransfer,
);
if (token_transfer_op.is_some()) {
let token_transfer = token_transfer_op.extract();
let receipt = receipt_op.extract();
let CompletedDestTokenTransfer {
token_receiver,
dest_token_address,
} = receipt;

assert!(
token_receiver == token_transfer.token_receiver &&
dest_token_address == token_transfer.dest_token_address,
ETokenTransferMismatch,
);
};

token_transfer_op.destroy_none();
receipt_op.destroy_none();

assert!(message_op.is_none(), ECCIPReceiveFailed);
message_op.destroy_none();
}

// =========================== Test Functions =========================== //

#[test_only]
Expand Down Expand Up @@ -312,3 +502,31 @@ public fun deconstruct_receiver_params_with_message_for_test(
message_op.destroy_none();
r.destroy_none();
}

#[test_only]
public fun deconstruct_receiver_params_v2_with_message_for_test(
_: &DestTransferCap,
receiver_package_id: address,
receiver_params: ReceiverParamsV2,
message: client::Any2SuiMessageV2,
) {
let ReceiverParamsV2 {
token_transfer: _,
message: message_op,
source_chain_selector: _,
receipt: mut r,
} = receiver_params;

if (r.is_some()) {
let completed_transfer = r.extract();
let CompletedDestTokenTransfer {
token_receiver: _,
dest_token_address: _,
} = completed_transfer;
};

message_op.destroy_none();
r.destroy_none();

client::consume_any2sui_message_v2(message, receiver_package_id);
}
Loading
Loading