From dfef1059b33b5d57656ea82ee26b12630d36c25b Mon Sep 17 00:00:00 2001 From: Sarp Date: Sat, 19 Oct 2024 12:36:05 +0300 Subject: [PATCH 1/3] forge install: chainlink v2.17.0 --- .gitmodules | 3 +++ lib/chainlink | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/chainlink diff --git a/.gitmodules b/.gitmodules index c47ecef..626c042 100644 --- a/.gitmodules +++ b/.gitmodules @@ -11,3 +11,6 @@ [submodule "lib/avalanche-interchain-token-transfer"] path = lib/avalanche-interchain-token-transfer url = https://github.com/ava-labs/avalanche-interchain-token-transfer +[submodule "lib/chainlink"] + path = lib/chainlink + url = https://github.com/smartcontractkit/chainlink diff --git a/lib/chainlink b/lib/chainlink new file mode 160000 index 0000000..5ebb632 --- /dev/null +++ b/lib/chainlink @@ -0,0 +1 @@ +Subproject commit 5ebb63266ca697f0649633641bbccb436c2c18bb From 2e4216222d984bf49fb2730ff25cd4ad3bdd2998 Mon Sep 17 00:00:00 2001 From: Sarp Date: Mon, 21 Oct 2024 21:51:20 +0300 Subject: [PATCH 2/3] cross-chain vrf request-response scheme via ICM --- .../CrossChainVRFConsumer.sol | 89 ++++++++++++ .../CrossChainVRFWrapper.sol | 128 ++++++++++++++++++ remappings.txt | 3 +- 3 files changed, 219 insertions(+), 1 deletion(-) create mode 100644 contracts/interchain-messaging/cross-chain-vrf-request-response/CrossChainVRFConsumer.sol create mode 100644 contracts/interchain-messaging/cross-chain-vrf-request-response/CrossChainVRFWrapper.sol diff --git a/contracts/interchain-messaging/cross-chain-vrf-request-response/CrossChainVRFConsumer.sol b/contracts/interchain-messaging/cross-chain-vrf-request-response/CrossChainVRFConsumer.sol new file mode 100644 index 0000000..ce4cc41 --- /dev/null +++ b/contracts/interchain-messaging/cross-chain-vrf-request-response/CrossChainVRFConsumer.sol @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +import "@chainlink/vrf/dev/interfaces/IVRFCoordinatorV2Plus.sol"; +import "@teleporter/ITeleporterReceiver.sol"; +import "@teleporter/ITeleporterMessenger.sol"; + +contract CrossChainVRFConsumer is ITeleporterReceiver { + + ITeleporterMessenger public teleporterMessenger; + address public vrfRequesterContract; + + bytes32 constant DATASOURCE_BLOCKCHAIN_ID = 0x7fc93d85c6d62c5b2ac0b519c87010ea5294012d1e407030d6acd0021cac10d5; + + struct CrossChainRequest { + bytes32 keyHash; + uint16 requestConfirmations; + uint32 callbackGasLimit; + uint32 numWords; + bool nativePayment; + } + + struct CrossChainResponse { + uint256 requestId; + uint256[] randomWords; + } + + event RandomWordsReceived(uint256 requestId); + + constructor(address _teleporterMessenger, address _vrfRequesterContract) { + teleporterMessenger = ITeleporterMessenger(_teleporterMessenger); + vrfRequesterContract = _vrfRequesterContract; + } + + function requestRandomWords( + bytes32 keyHash, + uint16 requestConfirmations, + uint32 callbackGasLimit, + uint32 numWords, + bool nativePayment, + uint32 requiredGasLimit + ) external { + // Create CrossChainRequest struct + CrossChainRequest memory crossChainRequest = CrossChainRequest({ + keyHash: keyHash, + requestConfirmations: requestConfirmations, + callbackGasLimit: callbackGasLimit, + numWords: numWords, + nativePayment: nativePayment + }); + // Send Teleporter message + bytes memory encodedMessage = abi.encode(crossChainRequest); + TeleporterMessageInput memory messageInput = TeleporterMessageInput({ + destinationBlockchainID: DATASOURCE_BLOCKCHAIN_ID, + destinationAddress: vrfRequesterContract, + feeInfo: TeleporterFeeInfo({ feeTokenAddress: address(0), amount: 0 }), + requiredGasLimit: requiredGasLimit, + allowedRelayerAddresses: new address[](0), + message: encodedMessage + }); + teleporterMessenger.sendCrossChainMessage(messageInput); + } + + function receiveTeleporterMessage( + bytes32 originChainID, + address originSenderAddress, + bytes calldata message + ) external { + require(originChainID == DATASOURCE_BLOCKCHAIN_ID, "Invalid originChainID"); + require(msg.sender == address(teleporterMessenger), "Caller is not the TeleporterMessenger"); + require(originSenderAddress == vrfRequesterContract, "Invalid sender"); + + // Decode the message to get the request ID and random words + CrossChainResponse memory response = abi.decode(message, (CrossChainResponse)); + + // Fulfill the request by calling the internal function + fulfillRandomWords(response.requestId, response.randomWords); + } + + function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal { + // Logic to handle the fulfillment of random words + // Implement your custom logic here + + // Emit event for received random words + emit RandomWordsReceived(requestId); + } + +} + diff --git a/contracts/interchain-messaging/cross-chain-vrf-request-response/CrossChainVRFWrapper.sol b/contracts/interchain-messaging/cross-chain-vrf-request-response/CrossChainVRFWrapper.sol new file mode 100644 index 0000000..5c784ca --- /dev/null +++ b/contracts/interchain-messaging/cross-chain-vrf-request-response/CrossChainVRFWrapper.sol @@ -0,0 +1,128 @@ +// (c) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +// SPDX-License-Identifier: Ecosystem + +pragma solidity ^0.8.4; + +import "@chainlink/vrf/dev/VRFConsumerBaseV2Plus.sol"; +import "@chainlink/vrf/dev/interfaces/IVRFCoordinatorV2Plus.sol"; +import "@chainlink/vrf/dev/libraries/VRFV2PlusClient.sol"; +import "@teleporter/ITeleporterReceiver.sol"; +import "@teleporter/ITeleporterMessenger.sol"; + +contract CrossChainVRFWrapper is ITeleporterReceiver, VRFConsumerBaseV2Plus { + + ITeleporterMessenger public teleporterMessenger; + + struct SubscriptionInfo { + uint256 subscriptionId; + bool isAuthorized; + } + mapping(address => SubscriptionInfo) public authorizedSubscriptions; + + // Avalanche Fuji VRF 2.5 Coordinator: + address s_vrfCoordinatorAddress = 0x5C210eF41CD1a72de73bF76eC39637bB0d3d7BEE; + + struct CrossChainRequest { + bytes32 keyHash; + uint16 requestConfirmations; + uint32 callbackGasLimit; + uint32 numWords; + bool nativePayment; + } + + struct CrossChainResponse { + uint256 requestId; + uint256[] randomWords; + } + + struct CrossChainReceiver { + bytes32 destinationBlockchainId; + address destinationAddress; + } + mapping(uint256 => CrossChainReceiver) public pendingRequests; + + constructor(address _teleporterMessenger) VRFConsumerBaseV2Plus(s_vrfCoordinatorAddress) { + teleporterMessenger = ITeleporterMessenger(_teleporterMessenger); + } + + function receiveTeleporterMessage( + bytes32 originChainID, + address originSenderAddress, + bytes calldata message + ) external { + require(msg.sender == address(teleporterMessenger), "Caller is not the TeleporterMessenger"); + // Verify that the origin sender address is authorized + require(authorizedSubscriptions[originSenderAddress].isAuthorized, "Origin sender is not authorized"); + uint256 subscriptionId = authorizedSubscriptions[originSenderAddress].subscriptionId; + // Verify that the subscription ID belongs to the correct owner + (,,,, address[] memory consumers) = s_vrfCoordinator.getSubscription(subscriptionId); + // Check wrapper contract is a consumer of the subscription + bool isConsumer = false; + for (uint256 i = 0; i < consumers.length; i++) { + if (consumers[i] == address(this)) { + isConsumer = true; + break; + } + } + require(isConsumer, "Contract is not a consumer of this subscription"); + // Decode message to get the VRF parameters + CrossChainRequest memory vrfMessage = abi.decode(message, (CrossChainRequest)); + // Request random words + VRFV2PlusClient.RandomWordsRequest memory req = VRFV2PlusClient.RandomWordsRequest({ + keyHash: vrfMessage.keyHash, + subId: subscriptionId, + requestConfirmations: vrfMessage.requestConfirmations, + callbackGasLimit: vrfMessage.callbackGasLimit, + numWords: vrfMessage.numWords, + extraArgs: VRFV2PlusClient._argsToBytes(VRFV2PlusClient.ExtraArgsV1({nativePayment: vrfMessage.nativePayment})) + }); + uint256 requestId = s_vrfCoordinator.requestRandomWords(req); + pendingRequests[requestId] = CrossChainReceiver({ + destinationBlockchainId: originChainID, + destinationAddress: originSenderAddress + }); + } + + function addAuthorizedAddress(address caller, uint256 subscriptionId) external { + // Verify that the subscription ID belongs to the correct owner + (,,, address owner,) = s_vrfCoordinator.getSubscription(subscriptionId); + require(owner == msg.sender, "Origin sender is not the owner of the subscription"); + // Add subscription + authorizedSubscriptions[caller] = SubscriptionInfo({ + subscriptionId: subscriptionId, + isAuthorized: true + }); + } + + function removeAuthorizedAddress(address _address) external { + require(authorizedSubscriptions[_address].isAuthorized, "Address is not authorized"); + uint256 subscriptionId = authorizedSubscriptions[_address].subscriptionId; + // Verify that the subscription ID belongs to the correct owner + (,,, address owner,) = s_vrfCoordinator.getSubscription(subscriptionId); + require(owner == msg.sender, "Origin sender is not the owner of the subscription"); + delete authorizedSubscriptions[_address]; + } + + function fulfillRandomWords(uint256 requestId, uint256[] calldata randomWords) internal override { + require(pendingRequests[requestId].destinationAddress != address(0), "Invalid request ID"); + // Create CrossChainResponse struct + CrossChainResponse memory crossChainResponse = CrossChainResponse({ + requestId: requestId, + randomWords: randomWords + }); + bytes memory encodedMessage = abi.encode(crossChainResponse); + // Send cross chain message using ITeleporterMessenger interface + TeleporterMessageInput memory messageInput = TeleporterMessageInput({ + destinationBlockchainID: pendingRequests[requestId].destinationBlockchainId, + destinationAddress: pendingRequests[requestId].destinationAddress, + feeInfo: TeleporterFeeInfo({ feeTokenAddress: address(0), amount: 0 }), + requiredGasLimit: 100000, + allowedRelayerAddresses: new address[](0), + message: encodedMessage + }); + teleporterMessenger.sendCrossChainMessage(messageInput); + delete pendingRequests[requestId]; + } +} diff --git a/remappings.txt b/remappings.txt index ea88f17..ddaf923 100644 --- a/remappings.txt +++ b/remappings.txt @@ -4,4 +4,5 @@ forge-std/=lib/forge-std/src/ @openzeppelin/contracts@4.8.1/=lib/openzeppelin-contracts/contracts/ @avalabs/subnet-evm-contracts@1.2.0/=lib/teleporter/contracts/lib/subnet-evm/contracts/ @teleporter/=lib/teleporter/contracts/src/Teleporter/ -@avalanche-interchain-token-transfer/=lib/avalanche-interchain-token-transfer/contracts/src/ \ No newline at end of file +@avalanche-interchain-token-transfer/=lib/avalanche-interchain-token-transfer/contracts/src/ +@chainlink/=lib/chainlink/contracts/src/v0.8/ \ No newline at end of file From ea94ef0f82e8905e15da5bb7c183708fe9733eeb Mon Sep 17 00:00:00 2001 From: Sarp Date: Mon, 21 Oct 2024 21:53:10 +0300 Subject: [PATCH 3/3] update licence text --- .../CrossChainVRFConsumer.sol | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/contracts/interchain-messaging/cross-chain-vrf-request-response/CrossChainVRFConsumer.sol b/contracts/interchain-messaging/cross-chain-vrf-request-response/CrossChainVRFConsumer.sol index ce4cc41..9e46adb 100644 --- a/contracts/interchain-messaging/cross-chain-vrf-request-response/CrossChainVRFConsumer.sol +++ b/contracts/interchain-messaging/cross-chain-vrf-request-response/CrossChainVRFConsumer.sol @@ -1,4 +1,8 @@ -// SPDX-License-Identifier: MIT +// (c) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +// SPDX-License-Identifier: Ecosystem + pragma solidity ^0.8.4; import "@chainlink/vrf/dev/interfaces/IVRFCoordinatorV2Plus.sol";