From 9c0251d93be6858e1b471e8339d23b6a96d61b8c Mon Sep 17 00:00:00 2001 From: benjamin852 Date: Thu, 11 Apr 2024 15:45:43 -0400 Subject: [PATCH 01/10] feat: added code snippets, contracts only --- .../evm/multichain-game/MultichainGame.sol | 104 ++++++++++++++++++ .../MultichainGameReciever.sol | 72 ++++++++++++ .../multichain-nft-mint/MultichainNFTMint.sol | 45 ++++++++ .../evm/multichain-swap/MultichainSwap.sol | 82 ++++++++++++++ package-lock.json | 60 ++++++++-- package.json | 1 + 6 files changed, 356 insertions(+), 8 deletions(-) create mode 100644 examples/evm/multichain-game/MultichainGame.sol create mode 100644 examples/evm/multichain-game/MultichainGameReciever.sol create mode 100644 examples/evm/multichain-nft-mint/MultichainNFTMint.sol create mode 100644 examples/evm/multichain-swap/MultichainSwap.sol diff --git a/examples/evm/multichain-game/MultichainGame.sol b/examples/evm/multichain-game/MultichainGame.sol new file mode 100644 index 00000000..46c71c8a --- /dev/null +++ b/examples/evm/multichain-game/MultichainGame.sol @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { IERC20 } from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; + +import { AxelarExecutable } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/executable/AxelarExecutable.sol'; +import { IAxelarGateway } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGateway.sol'; +import { IAxelarGasService } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGasService.sol'; + +contract MultichainGame is AxelarExecutable { + IAxelarGasService public immutable gasService; + + address public gameReceiver; + + string[] public uniqueTokens; + + constructor(address _gateway, address _gasService, address _gameReceiver) AxelarExecutable(_gateway) { + gasService = IAxelarGasService(_gasService); + gameReceiver = _gameReceiver; + } + + function guessNumber( + string memory _destChain, + string calldata _destContractAddr, + uint256 _guess, + string memory _symbol, + uint256 _amount + ) external payable { + require(_guess >= 1 && _guess <= 6, 'Invalid guess'); + + address tokenAddress = gateway.tokenAddresses(_symbol); + + require(tokenAddress != address(0), 'Invalid token'); + + if (bytes(_destChain).length == 0 && bytes(_destContractAddr).length == 0) { + //NO MULTICHAIN TX PLAYING ON SAME CHAIN + IERC20(tokenAddress).transferFrom(msg.sender, gameReceiver, _amount); + _checkIfWinner(msg.sender, _guess, _symbol, _amount); + } else { + //MULTICHAIN TX FROM CHAIN A TO CHAIN B + require(msg.value > 0, 'Insufficient gas'); + + bytes memory encodedGuess = abi.encode(msg.sender, _guess); + + IERC20(tokenAddress).transferFrom(msg.sender, address(this), _amount); + IERC20(tokenAddress).approve(address(gateway), _amount); + + gasService.payNativeGasForContractCallWithToken{ value: msg.value }( + address(this), + _destChain, + _destContractAddr, + encodedGuess, + _symbol, + _amount, + msg.sender + ); + + gateway.callContractWithToken(_destChain, _destContractAddr, encodedGuess, _symbol, _amount); + } + } + + function _executeWithToken( + string calldata _sourceChain, + string calldata, + bytes calldata _payload, + string calldata _symbol, + uint256 _amount + ) internal override { + address player = abi.decode(_payload, (address)); + address tokenAddress = gateway.tokenAddresses(_symbol); + IERC20(tokenAddress).transfer(player, _amount); + } + + function _checkIfWinner(address _player, uint256 _guess, string memory _tokenSymbol, uint256 _amount) internal { + _addUniqueTokenSymbol(_tokenSymbol); + uint256 diceResult = (block.timestamp % 6) + 1; + // uint256 diceResult = 5; for testing + + bool won = _guess == diceResult; + + if (won) _payOutAllTokensToWinner(_player); + } + + function _payOutAllTokensToWinner(address _player) internal { + for (uint i = 0; i < uniqueTokens.length; i++) { + string memory tokenSymbol = uniqueTokens[i]; + address tokenAddress = gateway.tokenAddresses(tokenSymbol); + uint256 transferAmount = IERC20(tokenAddress).balanceOf(address(this)); + IERC20(tokenAddress).transfer(_player, transferAmount); + } + } + + function _addUniqueTokenSymbol(string memory _tokenSymbol) internal { + bool found = false; + + for (uint256 i = 0; i < uniqueTokens.length; i++) { + if (keccak256(abi.encode(uniqueTokens[i])) == keccak256(abi.encode(_tokenSymbol))) { + found = true; + break; + } + } + if (!found) uniqueTokens.push(_tokenSymbol); + } +} diff --git a/examples/evm/multichain-game/MultichainGameReciever.sol b/examples/evm/multichain-game/MultichainGameReciever.sol new file mode 100644 index 00000000..b85fe5b2 --- /dev/null +++ b/examples/evm/multichain-game/MultichainGameReciever.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { IERC20 } from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; + +import { AxelarExecutable } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/executable/AxelarExecutable.sol'; +import { IAxelarGateway } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGateway.sol'; +import { IAxelarGasService } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGasService.sol'; + +contract MultichainGameReceiver is AxelarExecutable { + string[] public uniqueTokens; + + IAxelarGasService public immutable gasService; + + constructor(address _gateway, address _gasService) AxelarExecutable(_gateway) { + gasService = IAxelarGasService(_gasService); + } + + function _executeWithToken( + string calldata _sourceChain, + string calldata _sourceAddress, + bytes calldata _payload, + string calldata _symbol, + uint256 + ) internal override { + _checkIfWinner(_payload, _symbol, _sourceAddress, _sourceChain); + } + + function _checkIfWinner( + bytes calldata _payload, + string memory _tokenSymbol, + string calldata _sourceAddress, + string calldata _sourceChain + ) internal { + (address player, uint256 guess) = abi.decode(_payload, (address, uint256)); + + uint256 diceResult = (block.timestamp % 6) + 1; + // uint256 diceResult = 5; for testing + + _addUniqueTokenSymbol(_tokenSymbol); + + bool won = guess == diceResult; + + if (won) _payOutAllTokensToWinner(player, _sourceAddress, _sourceChain); + } + + function _addUniqueTokenSymbol(string memory _tokenSymbol) internal { + bool found = false; + + for (uint i = 0; i < uniqueTokens.length; i++) { + if (keccak256(abi.encode(uniqueTokens[i])) == keccak256(abi.encode(_tokenSymbol))) { + found = true; + break; + } + } + if (!found) uniqueTokens.push(_tokenSymbol); + } + + function _payOutAllTokensToWinner(address _player, string calldata _sourceAddress, string calldata _winnersChain) internal { + for (uint i = 0; i < uniqueTokens.length; i++) { + string memory tokenSymbol = uniqueTokens[i]; + + address tokenAddress = gateway.tokenAddresses(tokenSymbol); + + uint256 transferAmount = IERC20(tokenAddress).balanceOf(address(this)); + + IERC20(tokenAddress).approve(address(gateway), transferAmount); + + gateway.callContractWithToken(_winnersChain, _sourceAddress, abi.encode(_player), tokenSymbol, transferAmount); + } + } +} diff --git a/examples/evm/multichain-nft-mint/MultichainNFTMint.sol b/examples/evm/multichain-nft-mint/MultichainNFTMint.sol new file mode 100644 index 00000000..429771c7 --- /dev/null +++ b/examples/evm/multichain-nft-mint/MultichainNFTMint.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import { ERC721 } from '@openzeppelin/contracts/token/ERC721/ERC721.sol'; + +import { AxelarExecutable } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/executable/AxelarExecutable.sol'; +import { IAxelarGateway } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGateway.sol'; +import { IAxelarGasService } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGasService.sol'; + +contract MultichainNFTMint is AxelarExecutable { + IAxelarGasService public immutable gasService; + ERC721 nft; + + constructor(address _gateway, address _gasService, address _nftAddr) AxelarExecutable(_gateway) { + gasService = IAxelarGasService(_gasService); + nft = ERC721(_nftAddr); + } + + function mintNftOnDestChain( + string memory _destChain, + string memory _destContractAddr, + address receivingAddr, + uint256 _tokenId + ) external payable { + require(msg.value > 0, 'Gas payment required'); + + bytes memory mintNftPayload = abi.encodeWithSignature('safeMint(address,uint256)', receivingAddr, _tokenId); + + //pay gas from source chain + gasService.payNativeGasForContractCall{ value: msg.value }( + address(this), //sender + _destChain, + _destContractAddr, + mintNftPayload, + msg.sender + ); + + gateway.callContract(_destChain, _destContractAddr, mintNftPayload); + } + + function _execute(string calldata, string calldata, bytes calldata _payload) internal override { + (bool success, ) = address(nft).call(_payload); + require(success, 'safeMint() call failed'); + } +} diff --git a/examples/evm/multichain-swap/MultichainSwap.sol b/examples/evm/multichain-swap/MultichainSwap.sol new file mode 100644 index 00000000..44a6609c --- /dev/null +++ b/examples/evm/multichain-swap/MultichainSwap.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import { IERC20 } from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; + +import { AxelarExecutable } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/executable/AxelarExecutable.sol'; +import { IAxelarGateway } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGateway.sol'; +import { IAxelarGasService } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGasService.sol'; + +import '@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol'; + +contract InterchainDefi is AxelarExecutable { + address public wmatic //Mumbai: 0x9c3C9283D3e44854697Cd22D3Faa240Cfb032889 + address public weth //Mumbai: 0xA6FA4fB5f76172d178d61B04b0ecd319C5d1C0aa + + IAxelarGasService public immutable gasService; + + // mumbai router + ISwapRouter public immutable swapRouter = ISwapRouter(0xE592427A0AEce92De3Edee1F18E0157C05861564); + + constructor(address _gateway, address _gasService, address _wmatic, address _weth) AxelarExecutable(_gateway) { + gasService = IAxelarGasService(_gasService); + wmatic = _wmatic; + weth = _weth; + } + + // Example from live demo was: //WMATIC from Celo sent to Mumbai for the swap + function interchainSwap( + string memory _destChain, + string memory _destContractAddr, + string memory _symbol, + uint256 _amount + ) external payable { + require(msg.value > 0, 'Gas payment required'); + + uint24 poolFee = 3000; + + address tokenAddress = gateway.tokenAddresses(_symbol); + + IERC20(tokenAddress).transferFrom(msg.sender, address(this), _amount); + IERC20(tokenAddress).approve(address(gateway), _amount); + + ISwapRouter.ExactInputSingleParams memory swapParams = ISwapRouter.ExactInputSingleParams({ + tokenIn: wmatic, + tokenOut: weth, + fee: poolFee, + recipient: msg.sender, + deadline: block.timestamp + 1 days, + amountIn: _amount, + amountOutMinimum: 0, + sqrtPriceLimitX96: 0 + }); + + bytes memory encodedSwapPayload = abi.encode(swapParams); + + gasService.payNativeGasForContractCallWithToken{ value: msg.value }( + address(this), + _destChain, + _destContractAddr, + encodedSwapPayload, + _symbol, + _amount, + msg.sender + ); + + gateway.callContractWithToken(_destChain, _destContractAddr, encodedSwapPayload, _symbol, _amount); + } + + function _executeWithToken( + string calldata, + string calldata, + bytes calldata _payload, + string calldata, + uint256 _amount + ) internal override { + ISwapRouter.ExactInputSingleParams memory decodedGmpMessage = abi.decode(_payload, (ISwapRouter.ExactInputSingleParams)); + + IERC20(wmatic).approve(address(swapRouter), _amount); + + swapRouter.exactInputSingle(decodedGmpMessage); + } +} diff --git a/package-lock.json b/package-lock.json index 4ca3ca61..73e92142 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@grpc/proto-loader": "^0.7.10", "@multiversx/sdk-core": "^12.19.0", "@openzeppelin/contracts": "^4.5.0", + "@uniswap/v3-periphery": "^1.4.4", "axios": "^0.27.2", "bech32": "^2.0.0", "bip39": "^3.0.4", @@ -3588,6 +3589,50 @@ "dev": true, "peer": true }, + "node_modules/@uniswap/lib": { + "version": "4.0.1-alpha", + "resolved": "https://registry.npmjs.org/@uniswap/lib/-/lib-4.0.1-alpha.tgz", + "integrity": "sha512-f6UIliwBbRsgVLxIaBANF6w09tYqc6Y/qXdsrbEmXHyFA7ILiKrIwRFXe1yOg8M3cksgVsO9N7yuL2DdCGQKBA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/@uniswap/v2-core": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@uniswap/v2-core/-/v2-core-1.0.1.tgz", + "integrity": "sha512-MtybtkUPSyysqLY2U210NBDeCHX+ltHt3oADGdjqoThZaFRDKwM6k1Nb3F0A3hk5hwuQvytFWhrWHOEq6nVJ8Q==", + "engines": { + "node": ">=10" + } + }, + "node_modules/@uniswap/v3-core": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@uniswap/v3-core/-/v3-core-1.0.1.tgz", + "integrity": "sha512-7pVk4hEm00j9tc71Y9+ssYpO6ytkeI0y7WE9P6UcmNzhxPePwyAxImuhVsTqWK9YFvzgtvzJHi64pBl4jUzKMQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/@uniswap/v3-periphery": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/@uniswap/v3-periphery/-/v3-periphery-1.4.4.tgz", + "integrity": "sha512-S4+m+wh8HbWSO3DKk4LwUCPZJTpCugIsHrWR86m/OrUyvSqGDTXKFfc2sMuGXCZrD1ZqO3rhQsKgdWg3Hbb2Kw==", + "dependencies": { + "@openzeppelin/contracts": "3.4.2-solc-0.7", + "@uniswap/lib": "^4.0.1-alpha", + "@uniswap/v2-core": "^1.0.1", + "@uniswap/v3-core": "^1.0.0", + "base64-sol": "1.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@uniswap/v3-periphery/node_modules/@openzeppelin/contracts": { + "version": "3.4.2-solc-0.7", + "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-3.4.2-solc-0.7.tgz", + "integrity": "sha512-W6QmqgkADuFcTLzHL8vVoNBtkwjvQRpYIAom7KiUNoLKghyx3FgH0GBjt8NRvigV1ZmMOBllvE1By1C+bi8WpA==" + }, "node_modules/abbrev": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", @@ -4058,6 +4103,11 @@ } ] }, + "node_modules/base64-sol": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/base64-sol/-/base64-sol-1.0.1.tgz", + "integrity": "sha512-ld3cCNMeXt4uJXmLZBHFGMvVpK9KsLVEhPpFRXnvSVAqABKbuNZg/+dsq3NuM+wxFLb/UrVkz7m1ciWmkMfTbg==" + }, "node_modules/base64url": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", @@ -4233,7 +4283,6 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "dev": true, "funding": [ { "type": "github", @@ -8100,8 +8149,7 @@ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "extraneous": true, "os": [ - "darwin", - "linux" + "darwin" ], "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" @@ -11195,7 +11243,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, "funding": [ { "type": "github", @@ -11796,7 +11843,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.4.tgz", "integrity": "sha512-3vKuW0jV8J3XNTzvfyicFR5qvxrSAGl7KIhvgOu5cmWwM7tZRj3fMbj/pfIf4be7aznbc+prBWGjywox/g2Y6Q==", - "dev": true, "hasInstallScript": true, "dependencies": { "node-addon-api": "^2.0.0", @@ -12543,8 +12589,7 @@ "node_modules/node-addon-api": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", - "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==", - "dev": true + "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==" }, "node_modules/node-emoji": { "version": "1.11.0", @@ -12578,7 +12623,6 @@ "version": "4.8.0", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.0.tgz", "integrity": "sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==", - "dev": true, "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", diff --git a/package.json b/package.json index 7a613ca9..2ec5abf4 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "@grpc/proto-loader": "^0.7.10", "@multiversx/sdk-core": "^12.19.0", "@openzeppelin/contracts": "^4.5.0", + "@uniswap/v3-periphery": "^1.4.4", "axios": "^0.27.2", "bech32": "^2.0.0", "bip39": "^3.0.4", From 00134449af7ce44a23d13f1b1ba2b77e643f7044 Mon Sep 17 00:00:00 2001 From: benjamin852 Date: Fri, 12 Apr 2024 16:48:23 -0400 Subject: [PATCH 02/10] chore added readmes for code snippets --- examples/evm/multichain-game/README.md | 9 +++++++++ examples/evm/multichain-nft-mint/README.md | 7 +++++++ examples/evm/multichain-swap/README.md | 9 +++++++++ 3 files changed, 25 insertions(+) create mode 100644 examples/evm/multichain-game/README.md create mode 100644 examples/evm/multichain-nft-mint/README.md create mode 100644 examples/evm/multichain-swap/README.md diff --git a/examples/evm/multichain-game/README.md b/examples/evm/multichain-game/README.md new file mode 100644 index 00000000..22d913d2 --- /dev/null +++ b/examples/evm/multichain-game/README.md @@ -0,0 +1,9 @@ +# Multichain Game + +This example demonstrates a simple guessing game where users can guess a number between 1-6 and stake some funds for each guess. If the correct number is guessed then you win the pot! The `MultichainGameReceiver` exists on a single chain and can hold a variety of different tokens. It pays back the winner playing the game via the `MultiChainGame` contract on one of the satellite chains the game is deployed on. + +# Testnet +On testnet this can be tested from Celo chain where you can send WETH to the Polygon Mumbai testnet to conduct the swap on the Uniswap router. + +## Local Test +Local testing with Axelar Local Dev is WIP. \ No newline at end of file diff --git a/examples/evm/multichain-nft-mint/README.md b/examples/evm/multichain-nft-mint/README.md new file mode 100644 index 00000000..329e8c08 --- /dev/null +++ b/examples/evm/multichain-nft-mint/README.md @@ -0,0 +1,7 @@ +# Multichain Swap NFT Mint + +This example demonstrates how to mint an NFT on a destination chain via a message triggered from your source chain. It encodes a function signature of the `safeMint()` function and `calls` the function signature on the destination chain. + + +## Local Test +Local testing with Axelar Local Dev is WIP. diff --git a/examples/evm/multichain-swap/README.md b/examples/evm/multichain-swap/README.md new file mode 100644 index 00000000..1d19294f --- /dev/null +++ b/examples/evm/multichain-swap/README.md @@ -0,0 +1,9 @@ +# Multichain Swap Example + +This example demonstrates how to interact with the Uniswap router to do a cross chain swap. The contract can send a gmp message and a token from one blockchain to another blockchain and that message will tell the uniswap router to swap the token that was sent for another token. + +# Testnet +On testnet this can be tested from Celo chain where you can send WETH to the Polygon Mumbai testnet to conduct the swap on the Uniswap router. + +## Local Test +Local testing with Axelar Local Dev is WIP. \ No newline at end of file From 75d39c6bba16aa8ca7eca6541e5cc8d1f217e8a8 Mon Sep 17 00:00:00 2001 From: benjamin852 Date: Mon, 15 Apr 2024 14:56:36 -0400 Subject: [PATCH 03/10] fix: fixed versioning issue --- examples/evm/multichain-game/MultichainGame.sol | 2 +- examples/evm/multichain-game/MultichainGameReciever.sol | 2 +- examples/evm/multichain-nft-mint/MultichainNFTMint.sol | 2 +- examples/evm/multichain-swap/MultichainSwap.sol | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/evm/multichain-game/MultichainGame.sol b/examples/evm/multichain-game/MultichainGame.sol index 46c71c8a..14e1c85b 100644 --- a/examples/evm/multichain-game/MultichainGame.sol +++ b/examples/evm/multichain-game/MultichainGame.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.0; import { IERC20 } from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; diff --git a/examples/evm/multichain-game/MultichainGameReciever.sol b/examples/evm/multichain-game/MultichainGameReciever.sol index b85fe5b2..7a749372 100644 --- a/examples/evm/multichain-game/MultichainGameReciever.sol +++ b/examples/evm/multichain-game/MultichainGameReciever.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.0; import { IERC20 } from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; diff --git a/examples/evm/multichain-nft-mint/MultichainNFTMint.sol b/examples/evm/multichain-nft-mint/MultichainNFTMint.sol index 429771c7..df315246 100644 --- a/examples/evm/multichain-nft-mint/MultichainNFTMint.sol +++ b/examples/evm/multichain-nft-mint/MultichainNFTMint.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.20; +pragma solidity ^0.8.0; import { ERC721 } from '@openzeppelin/contracts/token/ERC721/ERC721.sol'; diff --git a/examples/evm/multichain-swap/MultichainSwap.sol b/examples/evm/multichain-swap/MultichainSwap.sol index 44a6609c..67cbba83 100644 --- a/examples/evm/multichain-swap/MultichainSwap.sol +++ b/examples/evm/multichain-swap/MultichainSwap.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.20; +pragma solidity ^0.8.0; import { IERC20 } from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; @@ -10,8 +10,8 @@ import { IAxelarGasService } from '@axelar-network/axelar-gmp-sdk-solidity/contr import '@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol'; contract InterchainDefi is AxelarExecutable { - address public wmatic //Mumbai: 0x9c3C9283D3e44854697Cd22D3Faa240Cfb032889 - address public weth //Mumbai: 0xA6FA4fB5f76172d178d61B04b0ecd319C5d1C0aa + address public wmatic; //Mumbai: 0x9c3C9283D3e44854697Cd22D3Faa240Cfb032889 + address public weth; //Mumbai: 0xA6FA4fB5f76172d178d61B04b0ecd319C5d1C0aa IAxelarGasService public immutable gasService; From f8df4a0b63d1beef6d90af17b2c0ef32ee5f2284 Mon Sep 17 00:00:00 2001 From: benjamin852 Date: Mon, 15 Apr 2024 15:00:00 -0400 Subject: [PATCH 04/10] chore: add linux for github action test --- package-lock.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 73e92142..7d571a5a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8149,7 +8149,8 @@ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "extraneous": true, "os": [ - "darwin" + "darwin", + "linux" ], "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" From 70f40f84a75b605edbba7fc95ca69d8e0fa02cb5 Mon Sep 17 00:00:00 2001 From: benjamin852 Date: Mon, 15 Apr 2024 16:18:00 -0400 Subject: [PATCH 05/10] feat: multichain nft working --- examples/evm/multichain-nft-mint/index.js | 54 +++++++++++++++++++ .../evm/multichain-nft-mint/mocks/MockNFT.sol | 12 +++++ 2 files changed, 66 insertions(+) create mode 100644 examples/evm/multichain-nft-mint/index.js create mode 100644 examples/evm/multichain-nft-mint/mocks/MockNFT.sol diff --git a/examples/evm/multichain-nft-mint/index.js b/examples/evm/multichain-nft-mint/index.js new file mode 100644 index 00000000..5fd9c01c --- /dev/null +++ b/examples/evm/multichain-nft-mint/index.js @@ -0,0 +1,54 @@ +'use strict'; + +const { + utils: { deployContract }, +} = require('@axelar-network/axelar-local-dev'); + +const MultichainNFTMint = rootRequire('./artifacts/examples/evm/multichain-nft-mint/MultichainNFTMint.sol/MultichainNFTMint.json'); +const MockNFT = rootRequire('./artifacts/examples/evm/multichain-nft-mint/mocks/MockNFT.sol/MockNFT.json'); + +async function deploy(chain, wallet) { + console.log(`Deploying Mock NFT for ${chain.name}.`); + chain.mockNFT = await deployContract(wallet, MockNFT); + console.log(`Deployed Mock NFT for ${chain.name} at ${chain.mockNFT.address}.`); + + console.log(`Deploying MultichainNFTMint for ${chain.name}.`); + chain.multichainNFT = await deployContract(wallet, MultichainNFTMint, [chain.gateway, chain.gasService, chain.mockNFT.address]); + chain.wallet = wallet; + console.log(`Deployed MultichainNFTMint for ${chain.name} at ${chain.multichainNFT.address}.`); +} + +async function execute(chains, wallet, options) { + const args = options.args || []; + const { source, destination, calculateBridgeFee } = options; + const receivingAddr = args[2]; + const tokenId = args[3]; + + async function logValue() { + console.log(`value at ${destination.name} is "${await destination.mockNFT.balanceOf(receivingAddr)}"`); + } + + console.log('--- Initially ---'); + await logValue(); + + const fee = await calculateBridgeFee(source, destination); + + const tx = await source.multichainNFT.mintNftOnDestChain(destination.name, destination.multichainNFT.address, receivingAddr, tokenId, { + value: fee, + }); + await tx.wait(); + + const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); + + while ((await destination.mockNFT.balanceOf(receivingAddr)) == '0') { + await sleep(1000); + } + + console.log('--- After ---'); + await logValue(); +} + +module.exports = { + deploy, + execute, +}; diff --git a/examples/evm/multichain-nft-mint/mocks/MockNFT.sol b/examples/evm/multichain-nft-mint/mocks/MockNFT.sol new file mode 100644 index 00000000..2d44563b --- /dev/null +++ b/examples/evm/multichain-nft-mint/mocks/MockNFT.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import '@openzeppelin/contracts/token/ERC721/ERC721.sol'; + +contract MockNFT is ERC721 { + constructor() ERC721('MockNFT', 'MOCK') {} + + function safeMint(address to, uint256 tokenId) external { + _safeMint(to, tokenId); + } +} From a1ce4b53b5a7d058e2f854311592b561f4d9d6dd Mon Sep 17 00:00:00 2001 From: benjamin852 Date: Mon, 15 Apr 2024 16:32:47 -0400 Subject: [PATCH 06/10] chore: added multichain nft mint readme --- examples/evm/multichain-nft-mint/README.md | 34 ++++++++++++++++++++-- examples/evm/multichain-nft-mint/index.js | 2 +- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/examples/evm/multichain-nft-mint/README.md b/examples/evm/multichain-nft-mint/README.md index 329e8c08..99d22d04 100644 --- a/examples/evm/multichain-nft-mint/README.md +++ b/examples/evm/multichain-nft-mint/README.md @@ -2,6 +2,36 @@ This example demonstrates how to mint an NFT on a destination chain via a message triggered from your source chain. It encodes a function signature of the `safeMint()` function and `calls` the function signature on the destination chain. +### Deployment -## Local Test -Local testing with Axelar Local Dev is WIP. +```bash +npm run deploy evm/multichain-nft-mint [local|testnet] +``` + +The aforementioned command pertains to specifying the intended environment for a project to execute on. It provides the option to choose between local and testnet environments by appending either `local` or `testnet` after the command. + +An example of its usage is demonstrated as follows: `npm run deploy evm/multichain-nft-mint local` or `npm run deploy evm/multichain-nft-mint testnet`. + +### Execution + +To execute the Multichain NFT example, use the following command: + +```bash +npm run execute evm/multichain-nft-mint [local|testnet] +``` + +### Example + +```bash +npm run deploy evm/multichain-nft-mint local +npm run execute evm/multichain-nft-mint local "Avalanche" "Fantom" "0xc5DcAC3e02f878FE995BF71b1Ef05153b71da8BE" 1 +``` + +Output: + +``` +--- Initially --- +balance of 0xc5DcAC3e02f878FE995BF71b1Ef05153b71da8BE at Fantom is "0" +--- After --- +balance of 0xc5DcAC3e02f878FE995BF71b1Ef05153b71da8BE at Fantom is "1" +``` diff --git a/examples/evm/multichain-nft-mint/index.js b/examples/evm/multichain-nft-mint/index.js index 5fd9c01c..448106fb 100644 --- a/examples/evm/multichain-nft-mint/index.js +++ b/examples/evm/multichain-nft-mint/index.js @@ -25,7 +25,7 @@ async function execute(chains, wallet, options) { const tokenId = args[3]; async function logValue() { - console.log(`value at ${destination.name} is "${await destination.mockNFT.balanceOf(receivingAddr)}"`); + console.log(`balance of ${receivingAddr} at ${destination.name} is "${await destination.mockNFT.balanceOf(receivingAddr)}"`); } console.log('--- Initially ---'); From b19738760a0e4a43e95eb1fd796a4ef9668ca49b Mon Sep 17 00:00:00 2001 From: benjamin852 Date: Tue, 16 Apr 2024 16:35:40 -0400 Subject: [PATCH 07/10] feat: added multi chain game execution and deployment require statement needs fix --- .../evm/multichain-game/MultichainGame.sol | 60 ++++----- ...eciever.sol => MultichainGameReceiver.sol} | 28 +++-- examples/evm/multichain-game/index.js | 114 ++++++++++++++++++ .../evm/multichain-swap/MultichainSwap.sol | 2 +- examples/tests/evm.test.js | 1 + 5 files changed, 155 insertions(+), 50 deletions(-) rename examples/evm/multichain-game/{MultichainGameReciever.sol => MultichainGameReceiver.sol} (65%) create mode 100644 examples/evm/multichain-game/index.js diff --git a/examples/evm/multichain-game/MultichainGame.sol b/examples/evm/multichain-game/MultichainGame.sol index 14e1c85b..463d641b 100644 --- a/examples/evm/multichain-game/MultichainGame.sol +++ b/examples/evm/multichain-game/MultichainGame.sol @@ -6,22 +6,24 @@ import { IERC20 } from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; import { AxelarExecutable } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/executable/AxelarExecutable.sol'; import { IAxelarGateway } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGateway.sol'; import { IAxelarGasService } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGasService.sol'; +import '@openzeppelin/contracts/utils/Strings.sol'; +import { AddressToString, StringToAddress } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/libs/AddressString.sol'; -contract MultichainGame is AxelarExecutable { - IAxelarGasService public immutable gasService; +import { MultichainGameReceiver } from './MultichainGameReceiver.sol'; - address public gameReceiver; +contract MultichainGame is AxelarExecutable { + using AddressToString for address; + using StringToAddress for string; - string[] public uniqueTokens; + IAxelarGasService public immutable gasService; - constructor(address _gateway, address _gasService, address _gameReceiver) AxelarExecutable(_gateway) { + constructor(address _gateway, address _gasService) AxelarExecutable(_gateway) { gasService = IAxelarGasService(_gasService); - gameReceiver = _gameReceiver; } function guessNumber( - string memory _destChain, - string calldata _destContractAddr, + string memory _destChain, //"" if not cross chain bet + string calldata _gameReceiver, uint256 _guess, string memory _symbol, uint256 _amount @@ -32,10 +34,10 @@ contract MultichainGame is AxelarExecutable { require(tokenAddress != address(0), 'Invalid token'); - if (bytes(_destChain).length == 0 && bytes(_destContractAddr).length == 0) { + if (bytes(_destChain).length == 0) { //NO MULTICHAIN TX PLAYING ON SAME CHAIN - IERC20(tokenAddress).transferFrom(msg.sender, gameReceiver, _amount); - _checkIfWinner(msg.sender, _guess, _symbol, _amount); + IERC20(tokenAddress).transferFrom(msg.sender, _gameReceiver.toAddress(), _amount); + _checkIfWinner(msg.sender, _guess, _gameReceiver.toAddress()); } else { //MULTICHAIN TX FROM CHAIN A TO CHAIN B require(msg.value > 0, 'Insufficient gas'); @@ -48,19 +50,19 @@ contract MultichainGame is AxelarExecutable { gasService.payNativeGasForContractCallWithToken{ value: msg.value }( address(this), _destChain, - _destContractAddr, + _gameReceiver, encodedGuess, _symbol, _amount, msg.sender ); - gateway.callContractWithToken(_destChain, _destContractAddr, encodedGuess, _symbol, _amount); + gateway.callContractWithToken(_destChain, _gameReceiver, encodedGuess, _symbol, _amount); } } function _executeWithToken( - string calldata _sourceChain, + string calldata, string calldata, bytes calldata _payload, string calldata _symbol, @@ -71,34 +73,16 @@ contract MultichainGame is AxelarExecutable { IERC20(tokenAddress).transfer(player, _amount); } - function _checkIfWinner(address _player, uint256 _guess, string memory _tokenSymbol, uint256 _amount) internal { - _addUniqueTokenSymbol(_tokenSymbol); - uint256 diceResult = (block.timestamp % 6) + 1; - // uint256 diceResult = 5; for testing + function _checkIfWinner(address _player, uint256 _guess, address _gameReceiver) internal { + uint256 diceResult = 5; //for testing + // uint256 diceResult = (block.timestamp % 6) + 1; bool won = _guess == diceResult; - if (won) _payOutAllTokensToWinner(_player); + if (won) _payoutWinner(_player, _gameReceiver); } - function _payOutAllTokensToWinner(address _player) internal { - for (uint i = 0; i < uniqueTokens.length; i++) { - string memory tokenSymbol = uniqueTokens[i]; - address tokenAddress = gateway.tokenAddresses(tokenSymbol); - uint256 transferAmount = IERC20(tokenAddress).balanceOf(address(this)); - IERC20(tokenAddress).transfer(_player, transferAmount); - } - } - - function _addUniqueTokenSymbol(string memory _tokenSymbol) internal { - bool found = false; - - for (uint256 i = 0; i < uniqueTokens.length; i++) { - if (keccak256(abi.encode(uniqueTokens[i])) == keccak256(abi.encode(_tokenSymbol))) { - found = true; - break; - } - } - if (!found) uniqueTokens.push(_tokenSymbol); + function _payoutWinner(address _player, address _gameReceiver) internal { + MultichainGameReceiver(_gameReceiver).payOutAllTokensToWinner(_player, address(this).toString(), Strings.toString(block.chainid)); } } diff --git a/examples/evm/multichain-game/MultichainGameReciever.sol b/examples/evm/multichain-game/MultichainGameReceiver.sol similarity index 65% rename from examples/evm/multichain-game/MultichainGameReciever.sol rename to examples/evm/multichain-game/MultichainGameReceiver.sol index 7a749372..2f8be987 100644 --- a/examples/evm/multichain-game/MultichainGameReciever.sol +++ b/examples/evm/multichain-game/MultichainGameReceiver.sol @@ -6,14 +6,18 @@ import { IERC20 } from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; import { AxelarExecutable } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/executable/AxelarExecutable.sol'; import { IAxelarGateway } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGateway.sol'; import { IAxelarGasService } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGasService.sol'; +import '@openzeppelin/contracts/utils/Strings.sol'; contract MultichainGameReceiver is AxelarExecutable { string[] public uniqueTokens; IAxelarGasService public immutable gasService; - constructor(address _gateway, address _gasService) AxelarExecutable(_gateway) { + address multichainGame; + + constructor(address _gateway, address _gasService, address _game) AxelarExecutable(_gateway) { gasService = IAxelarGasService(_gasService); + multichainGame = _game; } function _executeWithToken( @@ -34,14 +38,14 @@ contract MultichainGameReceiver is AxelarExecutable { ) internal { (address player, uint256 guess) = abi.decode(_payload, (address, uint256)); - uint256 diceResult = (block.timestamp % 6) + 1; - // uint256 diceResult = 5; for testing + uint256 diceResult = 5; // for testing + // uint256 diceResult = (block.timestamp % 6) + 1; _addUniqueTokenSymbol(_tokenSymbol); bool won = guess == diceResult; - if (won) _payOutAllTokensToWinner(player, _sourceAddress, _sourceChain); + if (won) payOutAllTokensToWinner(player, _sourceAddress, _sourceChain); } function _addUniqueTokenSymbol(string memory _tokenSymbol) internal { @@ -56,17 +60,19 @@ contract MultichainGameReceiver is AxelarExecutable { if (!found) uniqueTokens.push(_tokenSymbol); } - function _payOutAllTokensToWinner(address _player, string calldata _sourceAddress, string calldata _winnersChain) internal { + function payOutAllTokensToWinner(address _player, string calldata _sourceAddress, string calldata _winnersChain) public { + // TODO fix req statement + // require(msg.sender == address(this) || msg.sender == multichainGame, 'invalid sender'); for (uint i = 0; i < uniqueTokens.length; i++) { string memory tokenSymbol = uniqueTokens[i]; - address tokenAddress = gateway.tokenAddresses(tokenSymbol); - uint256 transferAmount = IERC20(tokenAddress).balanceOf(address(this)); - - IERC20(tokenAddress).approve(address(gateway), transferAmount); - - gateway.callContractWithToken(_winnersChain, _sourceAddress, abi.encode(_player), tokenSymbol, transferAmount); + if (keccak256(abi.encode(_winnersChain)) == keccak256(abi.encode(Strings.toString(block.chainid)))) { + IERC20(tokenAddress).transfer(_player, transferAmount); + } else { + IERC20(tokenAddress).approve(address(gateway), transferAmount); + gateway.callContractWithToken(_winnersChain, _sourceAddress, abi.encode(_player), tokenSymbol, transferAmount); + } } } } diff --git a/examples/evm/multichain-game/index.js b/examples/evm/multichain-game/index.js new file mode 100644 index 00000000..039d9404 --- /dev/null +++ b/examples/evm/multichain-game/index.js @@ -0,0 +1,114 @@ +'use strict'; + +const { + utils: { deployContract }, +} = require('@axelar-network/axelar-local-dev'); + +const MultichainGame = rootRequire('./artifacts/examples/evm/multichain-game/MultichainGame.sol/MultichainGame.json'); +const MultichainGameReceiver = rootRequire( + './artifacts/examples/evm/multichain-game/MultichainGameReceiver.sol/MultichainGameReceiver.json', +); + +async function deploy(chain, wallet) { + chain.multichainGame = await deployContract(wallet, MultichainGame, [chain.gateway, chain.gasService]); + console.log(`Deployed Multichain Game for ${chain.name} at address: ${chain.multichainGame.address}.`); + + chain.multichainReceiver = await deployContract(wallet, MultichainGameReceiver, [ + chain.gateway, + chain.gasService, + chain.multichainGame.address, + ]); + console.log(`Deployed Multichain Receiver for ${chain.name} at ${chain.multichainReceiver.address}.`); + + chain.wallet = wallet; +} + +async function execute(chains, wallet, options) { + const args = options.args || []; + let guess; + const { source, destination, calculateBridgeFee } = options; + guess = args[2]; + const token = args[3]; + const amount = args[4]; + + const originalBalance = await source.usdc.balanceOf(wallet.address); + + function log(guess) { + console.log(`guess on ${destination.name} chain guess was: ${guess}`); + guess != 5 ? console.log('you guessed wrong!') : console.log('you guessed right!'); + } + + console.log('--- Initially ---'); + console.log(`your balance at ${source.name} is ${originalBalance.toString()}`); + console.log(`pot balance at ${destination.name} is ${await destination.usdc.balanceOf(destination.multichainReceiver.address)}`); + + const fee = await calculateBridgeFee(source, destination); + + //approve on dest + const destChainApproveTx = await destination.usdc.approve(destination.multichainGame.address, amount); + await destChainApproveTx.wait(); + + //approve on src + const srcChainApproveTx = await source.usdc.approve(source.multichainGame.address, amount * 2); + await srcChainApproveTx.wait(); + + //guess incorrect on dest + const incorrectGuessTxSameChain = await destination.multichainGame.guessNumber( + '', + destination.multichainReceiver.address, + guess === 5 ? (guess = 4) : guess, + token, + amount, + ); + await incorrectGuessTxSameChain.wait(); + log(guess); + + //guess incorrect 1 on src + const incorrectGuessTxMultiChain = await source.multichainGame.guessNumber( + destination.name, + destination.multichainReceiver.address, + guess != 5 ? (guess = 4) : guess, + token, + amount, + { value: fee }, + ); + await incorrectGuessTxMultiChain.wait(); + log(guess); + + console.log(`pot balance is now ${await destination.usdc.balanceOf(destination.multichainReceiver.address)}`); + + //guess correct on src + const correctGuessTxMultiChain = await source.multichainGame.guessNumber( + destination.name, + destination.multichainReceiver.address, + guess != 5 ? (guess = 5) : guess, + token, + amount, + { + value: fee, + }, + ); + await correctGuessTxMultiChain.wait(); + log(guess); + + console.log('---- AFTER CORRECT GUESS ----'); + const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); + + while (true) { + const updatedBalance = await source.usdc.balanceOf(wallet.address); + if (updatedBalance.gt(originalBalance)) { + const potUpdatedBalance = await destination.usdc.balanceOf(destination.multichainReceiver.address); + const userUpdatedBalance = await source.usdc.balanceOf(wallet.address); + console.log(`pot balance at ${destination.name} is now ${potUpdatedBalance}`); + console.log(`your balance at ${source.name} is now ${userUpdatedBalance}`); + break; + } + await sleep(6000); + + } +} + +module.exports = { + deploy, + execute, +}; diff --git a/examples/evm/multichain-swap/MultichainSwap.sol b/examples/evm/multichain-swap/MultichainSwap.sol index 67cbba83..17ffa21b 100644 --- a/examples/evm/multichain-swap/MultichainSwap.sol +++ b/examples/evm/multichain-swap/MultichainSwap.sol @@ -9,7 +9,7 @@ import { IAxelarGasService } from '@axelar-network/axelar-gmp-sdk-solidity/contr import '@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol'; -contract InterchainDefi is AxelarExecutable { +contract MultichainSwap is AxelarExecutable { address public wmatic; //Mumbai: 0x9c3C9283D3e44854697Cd22D3Faa240Cfb032889 address public weth; //Mumbai: 0xA6FA4fB5f76172d178d61B04b0ecd319C5d1C0aa diff --git a/examples/tests/evm.test.js b/examples/tests/evm.test.js index 13f0bfa3..b4b414b6 100644 --- a/examples/tests/evm.test.js +++ b/examples/tests/evm.test.js @@ -21,6 +21,7 @@ const examples = [ 'call-contract-with-token-express', 'cross-chain-token', 'deposit-address', + 'multichain-nft-mint', 'nonced-execution', 'nft-linker', 'send-ack', From d4fecd37a16aeee290d698144e22f284051ea93c Mon Sep 17 00:00:00 2001 From: benjamin852 Date: Thu, 18 Apr 2024 14:23:40 -0400 Subject: [PATCH 08/10] chore: broke up pay receiver into two functions and added readme for game ex --- .../MultichainGameReceiver.sol | 15 +++++-- examples/evm/multichain-game/README.md | 41 +++++++++++++++++-- 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/examples/evm/multichain-game/MultichainGameReceiver.sol b/examples/evm/multichain-game/MultichainGameReceiver.sol index 2f8be987..c1de7493 100644 --- a/examples/evm/multichain-game/MultichainGameReceiver.sol +++ b/examples/evm/multichain-game/MultichainGameReceiver.sol @@ -20,6 +20,11 @@ contract MultichainGameReceiver is AxelarExecutable { multichainGame = _game; } + function payOutAllTokensToWinnerSameChain(address _player, string calldata _sourceAddress, string calldata _winnersChain) external { + require(msg.sender == multichainGame, 'invalid sender'); + _payout(_player, _sourceAddress, _winnersChain); + } + function _executeWithToken( string calldata _sourceChain, string calldata _sourceAddress, @@ -45,7 +50,7 @@ contract MultichainGameReceiver is AxelarExecutable { bool won = guess == diceResult; - if (won) payOutAllTokensToWinner(player, _sourceAddress, _sourceChain); + if (won) _payOutAllTokensToWinnerInternal(player, _sourceAddress, _sourceChain); } function _addUniqueTokenSymbol(string memory _tokenSymbol) internal { @@ -60,9 +65,11 @@ contract MultichainGameReceiver is AxelarExecutable { if (!found) uniqueTokens.push(_tokenSymbol); } - function payOutAllTokensToWinner(address _player, string calldata _sourceAddress, string calldata _winnersChain) public { - // TODO fix req statement - // require(msg.sender == address(this) || msg.sender == multichainGame, 'invalid sender'); + function _payOutAllTokensToWinnerInternal(address _player, string calldata _sourceAddress, string calldata _winnersChain) internal { + _payout(_player, _sourceAddress, _winnersChain); + } + + function _payout(address _player, string calldata _sourceAddress, string calldata _winnersChain) internal { for (uint i = 0; i < uniqueTokens.length; i++) { string memory tokenSymbol = uniqueTokens[i]; address tokenAddress = gateway.tokenAddresses(tokenSymbol); diff --git a/examples/evm/multichain-game/README.md b/examples/evm/multichain-game/README.md index 22d913d2..61a1dd9c 100644 --- a/examples/evm/multichain-game/README.md +++ b/examples/evm/multichain-game/README.md @@ -2,8 +2,41 @@ This example demonstrates a simple guessing game where users can guess a number between 1-6 and stake some funds for each guess. If the correct number is guessed then you win the pot! The `MultichainGameReceiver` exists on a single chain and can hold a variety of different tokens. It pays back the winner playing the game via the `MultiChainGame` contract on one of the satellite chains the game is deployed on. -# Testnet -On testnet this can be tested from Celo chain where you can send WETH to the Polygon Mumbai testnet to conduct the swap on the Uniswap router. +### Deployment -## Local Test -Local testing with Axelar Local Dev is WIP. \ No newline at end of file +```bash + npm run deploy evm/multichain-game [local|testnet] +``` +This will deploy the MultichainGame and MultichainGame Receiver contract. + +### Execution + +To execute the Game run the following command. + +```bash + npm run execute evm/multichain-game local [local|testnet] + ``` + + ### Example + +```bash +npm run deploy evm/multichain-game local +npm run execute evm/multichain-game local "Avalanche" "Fantom" 3 "aUSDC" 3 +``` + +Output: +``` +--- Initially --- +your balance at Avalanche is 1000000000000000000 +pot balance at Fantom is 0 +guess on Fantom chain guess was: 3 +you guessed wrong! +guess on Fantom chain guess was: 4 +you guessed wrong! +pot balance is now 3 +guess on Fantom chain guess was: 5 +you guessed right! +---- AFTER CORRECT GUESS ---- +pot balance at Fantom is now 0 +your balance at Avalanche is now 1000000000000000003 +``` \ No newline at end of file From 66914fbf54361cd55209ed066caf373227a796e4 Mon Sep 17 00:00:00 2001 From: benjamin852 Date: Thu, 18 Apr 2024 14:31:42 -0400 Subject: [PATCH 09/10] fix: fix payOutAllTokensToWinnerSameChain function call --- examples/evm/multichain-game/MultichainGame.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/evm/multichain-game/MultichainGame.sol b/examples/evm/multichain-game/MultichainGame.sol index 463d641b..3c9e29b1 100644 --- a/examples/evm/multichain-game/MultichainGame.sol +++ b/examples/evm/multichain-game/MultichainGame.sol @@ -83,6 +83,6 @@ contract MultichainGame is AxelarExecutable { } function _payoutWinner(address _player, address _gameReceiver) internal { - MultichainGameReceiver(_gameReceiver).payOutAllTokensToWinner(_player, address(this).toString(), Strings.toString(block.chainid)); + MultichainGameReceiver(_gameReceiver).payOutAllTokensToWinnerSameChain(_player, address(this).toString(), Strings.toString(block.chainid)); } } From 8335064f4dda3b8b0c21aa075e535756495276d1 Mon Sep 17 00:00:00 2001 From: npty Date: Fri, 19 Apr 2024 17:18:11 +0700 Subject: [PATCH 10/10] chore: comment out multichain-nft-mint test --- examples/tests/evm.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/tests/evm.test.js b/examples/tests/evm.test.js index b4b414b6..75a2961b 100644 --- a/examples/tests/evm.test.js +++ b/examples/tests/evm.test.js @@ -21,7 +21,7 @@ const examples = [ 'call-contract-with-token-express', 'cross-chain-token', 'deposit-address', - 'multichain-nft-mint', + // 'multichain-nft-mint', 'nonced-execution', 'nft-linker', 'send-ack',