From 20a7cd92cd73c4f8230bfd910ce7878d2f55f9ff Mon Sep 17 00:00:00 2001 From: Denis Fadeev Date: Fri, 24 Jan 2025 15:50:18 +0300 Subject: [PATCH 1/4] feat: handle failed transfers inside onAbort --- .../nft/contracts/evm/UniversalNFTCore.sol | 4 +- .../contracts/zetachain/UniversalNFTCore.sol | 38 ++++++++++++++++--- contracts/nft/package.json | 2 +- contracts/nft/scripts/localnet.sh | 2 +- contracts/nft/yarn.lock | 20 +++++++--- 5 files changed, 52 insertions(+), 14 deletions(-) diff --git a/contracts/nft/contracts/evm/UniversalNFTCore.sol b/contracts/nft/contracts/evm/UniversalNFTCore.sol index 41c272e..6d739a9 100644 --- a/contracts/nft/contracts/evm/UniversalNFTCore.sol +++ b/contracts/nft/contracts/evm/UniversalNFTCore.sol @@ -128,7 +128,7 @@ abstract contract UniversalNFTCore is gateway.call( universal, message, - RevertOptions(address(this), false, address(0), message, 0) + RevertOptions(address(this), false, universal, message, 0) ); } else { gateway.depositAndCall{value: msg.value}( @@ -137,7 +137,7 @@ abstract contract UniversalNFTCore is RevertOptions( address(this), true, - address(0), + universal, abi.encode(receiver, tokenId, uri, msg.sender), gasLimitAmount ) diff --git a/contracts/nft/contracts/zetachain/UniversalNFTCore.sol b/contracts/nft/contracts/zetachain/UniversalNFTCore.sol index 7c81233..7dc22c5 100644 --- a/contracts/nft/contracts/zetachain/UniversalNFTCore.sol +++ b/contracts/nft/contracts/zetachain/UniversalNFTCore.sol @@ -11,6 +11,15 @@ import "@zetachain/protocol-contracts/contracts/zevm/interfaces/IWZETA.sol"; import "@zetachain/protocol-contracts/contracts/zevm/GatewayZEVM.sol"; import {SwapHelperLib} from "@zetachain/toolkit/contracts/SwapHelperLib.sol"; +struct AbortContext { + bytes sender; + address asset; + uint256 amount; + bool outgoing; + uint256 chainID; + bytes revertMessage; +} + /** * @title UniversalNFTCore * @dev This abstract contract provides the core logic for Universal NFTs. It is designed @@ -167,8 +176,8 @@ abstract contract UniversalNFTCore is RevertOptions memory revertOptions = RevertOptions( address(this), true, - address(0), - abi.encode(tokenId, uri, msg.sender), + address(this), + abi.encode(receiver, tokenId, uri, msg.sender), gasLimitAmount ); @@ -243,7 +252,7 @@ abstract contract UniversalNFTCore is address(this), true, address(0), - abi.encode(tokenId, uri, sender), + abi.encode(receiver, tokenId, uri, sender), 0 ) ); @@ -256,9 +265,9 @@ abstract contract UniversalNFTCore is * @param context Metadata about the failed call. */ function onRevert(RevertContext calldata context) external onlyGateway { - (uint256 tokenId, string memory uri, address sender) = abi.decode( + (, uint256 tokenId, string memory uri, address sender) = abi.decode( context.revertMessage, - (uint256, string, address) + (address, uint256, string, address) ); _safeMint(sender, tokenId); @@ -266,6 +275,25 @@ abstract contract UniversalNFTCore is emit TokenTransferReverted(sender, tokenId, uri); } + /** + * @notice onAbort is executed when a transfer from one connected chain to another + * fails inside onCall, for example, because the amount of tokens supplied is not + * sufficient to cover the withdraw gas fee to the destination and also not enough + * to cover withdraw gas fee to the source chain. In this scenario we don't have + * enough tokens to send NFT cross-chain, so the best thing we can do is to transfer + * the NFT to the original sender on ZetaChain. + * @param context Metadata about the failed call. + */ + function onAbort(AbortContext calldata context) external { + (, uint256 tokenId, string memory uri, address sender) = abi.decode( + context.revertMessage, + (address, uint256, string, address) + ); + _safeMint(sender, tokenId); + _setTokenURI(tokenId, uri); + emit TokenTransferReverted(sender, tokenId, uri); + } + /** * @notice Returns the metadata URI for an NFT. * @param tokenId The ID of the token. diff --git a/contracts/nft/package.json b/contracts/nft/package.json index a857c28..44a6e30 100644 --- a/contracts/nft/package.json +++ b/contracts/nft/package.json @@ -31,7 +31,7 @@ "@types/validator": "^13.12.2", "@typescript-eslint/eslint-plugin": "^5.59.9", "@typescript-eslint/parser": "^5.59.9", - "@zetachain/localnet": "4.0.0-rc6", + "@zetachain/localnet": "5.0.0-rc1", "@zetachain/toolkit": "13.0.0-rc8", "axios": "^1.3.6", "chai": "^4.2.0", diff --git a/contracts/nft/scripts/localnet.sh b/contracts/nft/scripts/localnet.sh index 3411386..907ba6e 100755 --- a/contracts/nft/scripts/localnet.sh +++ b/contracts/nft/scripts/localnet.sh @@ -4,7 +4,7 @@ set -e set -x set -o pipefail -if [ "$1" = "start" ]; then npx hardhat localnet --exit-on-error & sleep 10; fi +if [ "$1" = "start" ]; then npx hardhat localnet & sleep 10; fi function balance() { local ZETACHAIN=$(cast call "$CONTRACT_ZETACHAIN" "balanceOf(address)(uint256)" "$SENDER") diff --git a/contracts/nft/yarn.lock b/contracts/nft/yarn.lock index 92a9705..f1514c6 100644 --- a/contracts/nft/yarn.lock +++ b/contracts/nft/yarn.lock @@ -2564,15 +2564,15 @@ typescript "5.5.4" zod "3.22.4" -"@zetachain/localnet@4.0.0-rc6": - version "4.0.0-rc6" - resolved "https://registry.yarnpkg.com/@zetachain/localnet/-/localnet-4.0.0-rc6.tgz#9b36f5ab0e8fac766d63cfca4b1cab6a263bdd5d" - integrity sha512-DGKspMAJZLUrgNirc3NzFYg9jRfaOyuF5ePj85D93qAA//f8lOsXpmh/6Bvq/MrEscCLpVavgVP7+ePy4KJ2Fw== +"@zetachain/localnet@5.0.0-rc1": + version "5.0.0-rc1" + resolved "https://registry.yarnpkg.com/@zetachain/localnet/-/localnet-5.0.0-rc1.tgz#920803084156a7b86fe763541b83a24833fb54d6" + integrity sha512-HHQ01dp9fxoA8WBBEXLmh5p/GMqVdd/0oYkdzzFZQ52UNXi+ucc4B/wdpBlKwub06r6Q8edPXiPfOjGPFthcMQ== dependencies: "@inquirer/prompts" "^5.5.0" "@uniswap/v2-core" "^1.0.1" "@uniswap/v2-periphery" "^1.1.0-beta.0" - "@zetachain/protocol-contracts" "11.0.0-rc3" + "@zetachain/protocol-contracts" "11.0.0-rc4" ansis "^3.3.2" concurrently "^8.2.2" ethers "^6.13.2" @@ -2596,6 +2596,16 @@ "@zetachain/networks" "^10.0.0" ethers "5.6.8" +"@zetachain/protocol-contracts@11.0.0-rc4": + version "11.0.0-rc4" + resolved "https://registry.yarnpkg.com/@zetachain/protocol-contracts/-/protocol-contracts-11.0.0-rc4.tgz#2e2df98734793873e9c50629f6ec9f5eec6f9f54" + integrity sha512-7MJzEyUad7JgHucveIhtU8aaPkoMMzsfhKkh9MDMdxUzlaOmmxrQw2hi5B2b7UPxw2K9vFffkmIox6gd6c1+Yw== + dependencies: + "@openzeppelin/contracts" "^5.0.2" + "@openzeppelin/contracts-upgradeable" "^5.0.2" + "@zetachain/networks" "^10.0.0" + ethers "5.6.8" + "@zetachain/protocol-contracts@9.0.0": version "9.0.0" resolved "https://registry.yarnpkg.com/@zetachain/protocol-contracts/-/protocol-contracts-9.0.0.tgz#c20ad5da43f6f3676f31556b303d1cb4ea17357e" From 273981d65a3f692f7071ed7869d5357074af0b76 Mon Sep 17 00:00:00 2001 From: Denis Fadeev Date: Fri, 24 Jan 2025 16:59:25 +0300 Subject: [PATCH 2/4] error --- contracts/nft/contracts/shared/UniversalNFTEvents.sol | 6 ++++++ contracts/nft/contracts/zetachain/UniversalNFTCore.sol | 3 +-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/contracts/nft/contracts/shared/UniversalNFTEvents.sol b/contracts/nft/contracts/shared/UniversalNFTEvents.sol index 79747b2..96b8964 100644 --- a/contracts/nft/contracts/shared/UniversalNFTEvents.sol +++ b/contracts/nft/contracts/shared/UniversalNFTEvents.sol @@ -21,6 +21,12 @@ contract UniversalNFTEvents { uint256 indexed tokenId, string uri ); + event TokenTransferAborted( + address indexed sender, + uint256 indexed tokenId, + string uri, + bool outgoing + ); event TokenTransferToDestination( address indexed destination, address indexed sender, diff --git a/contracts/nft/contracts/zetachain/UniversalNFTCore.sol b/contracts/nft/contracts/zetachain/UniversalNFTCore.sol index 7dc22c5..d2a4932 100644 --- a/contracts/nft/contracts/zetachain/UniversalNFTCore.sol +++ b/contracts/nft/contracts/zetachain/UniversalNFTCore.sol @@ -269,7 +269,6 @@ abstract contract UniversalNFTCore is context.revertMessage, (address, uint256, string, address) ); - _safeMint(sender, tokenId); _setTokenURI(tokenId, uri); emit TokenTransferReverted(sender, tokenId, uri); @@ -291,7 +290,7 @@ abstract contract UniversalNFTCore is ); _safeMint(sender, tokenId); _setTokenURI(tokenId, uri); - emit TokenTransferReverted(sender, tokenId, uri); + emit TokenTransferAborted(sender, tokenId, uri, context.outgoing); } /** From 7ed7c4e4eea0232afd525a27bc2cd07563dca603 Mon Sep 17 00:00:00 2001 From: Denis Fadeev Date: Fri, 24 Jan 2025 16:59:47 +0300 Subject: [PATCH 3/4] localnet --- contracts/nft/scripts/localnet.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/nft/scripts/localnet.sh b/contracts/nft/scripts/localnet.sh index 907ba6e..3411386 100755 --- a/contracts/nft/scripts/localnet.sh +++ b/contracts/nft/scripts/localnet.sh @@ -4,7 +4,7 @@ set -e set -x set -o pipefail -if [ "$1" = "start" ]; then npx hardhat localnet & sleep 10; fi +if [ "$1" = "start" ]; then npx hardhat localnet --exit-on-error & sleep 10; fi function balance() { local ZETACHAIN=$(cast call "$CONTRACT_ZETACHAIN" "balanceOf(address)(uint256)" "$SENDER") From 38f0d7827e4194d994d8989c0d3cf9ce9745e1b2 Mon Sep 17 00:00:00 2001 From: Denis Fadeev Date: Fri, 24 Jan 2025 17:27:10 +0300 Subject: [PATCH 4/4] FT --- .../contracts/evm/UniversalTokenCore.sol | 4 +-- .../contracts/shared/UniversalTokenEvents.sol | 1 + .../zetachain/UniversalTokenCore.sol | 28 +++++++++++++++---- contracts/token/package.json | 6 ++-- contracts/token/yarn.lock | 20 +++++++++---- 5 files changed, 44 insertions(+), 15 deletions(-) diff --git a/contracts/token/contracts/evm/UniversalTokenCore.sol b/contracts/token/contracts/evm/UniversalTokenCore.sol index b469085..f59c121 100644 --- a/contracts/token/contracts/evm/UniversalTokenCore.sol +++ b/contracts/token/contracts/evm/UniversalTokenCore.sol @@ -127,7 +127,7 @@ abstract contract UniversalTokenCore is gateway.call( universal, message, - RevertOptions(address(this), false, address(0), message, 0) + RevertOptions(address(this), false, universal, message, 0) ); } else { gateway.depositAndCall{value: msg.value}( @@ -136,7 +136,7 @@ abstract contract UniversalTokenCore is RevertOptions( address(this), true, - address(0), + universal, abi.encode(amount, msg.sender), gasLimitAmount ) diff --git a/contracts/token/contracts/shared/UniversalTokenEvents.sol b/contracts/token/contracts/shared/UniversalTokenEvents.sol index dd7a5f1..d10ecc7 100644 --- a/contracts/token/contracts/shared/UniversalTokenEvents.sol +++ b/contracts/token/contracts/shared/UniversalTokenEvents.sol @@ -12,6 +12,7 @@ contract UniversalTokenEvents { ); event TokenTransferReceived(address indexed receiver, uint256 amount); event TokenTransferReverted(address indexed sender, uint256 amount); + event TokenTransferAborted(address indexed sender, uint256 amount); event TokenTransferToDestination( address indexed destination, address indexed sender, diff --git a/contracts/token/contracts/zetachain/UniversalTokenCore.sol b/contracts/token/contracts/zetachain/UniversalTokenCore.sol index c49ee3b..0ba1eeb 100644 --- a/contracts/token/contracts/zetachain/UniversalTokenCore.sol +++ b/contracts/token/contracts/zetachain/UniversalTokenCore.sol @@ -11,6 +11,15 @@ import {SwapHelperLib} from "@zetachain/toolkit/contracts/SwapHelperLib.sol"; import "../shared/UniversalTokenEvents.sol"; +struct AbortContext { + bytes sender; + address asset; + uint256 amount; + bool outgoing; + uint256 chainID; + bytes revertMessage; +} + /** * @title UniversalTokenCore * @dev This abstract contract provides the core logic for Universal Tokens. It is designed @@ -161,8 +170,8 @@ abstract contract UniversalTokenCore is RevertOptions memory revertOptions = RevertOptions( address(this), true, - address(0), - abi.encode(amount, msg.sender), + address(this), + abi.encode(receiver, amount, msg.sender), gasLimitAmount ); @@ -230,7 +239,7 @@ abstract contract UniversalTokenCore is address(this), true, address(0), - abi.encode(tokenAmount, sender), + abi.encode(receiver, tokenAmount, sender), 0 ) ); @@ -243,11 +252,20 @@ abstract contract UniversalTokenCore is * @param context Metadata about the failed call. */ function onRevert(RevertContext calldata context) external onlyGateway { - (uint256 amount, address sender) = abi.decode( + (, uint256 amount, address sender) = abi.decode( context.revertMessage, - (uint256, address) + (address, uint256, address) ); _mint(sender, amount); emit TokenTransferReverted(sender, amount); } + + function onAbort(AbortContext calldata context) external { + (, uint256 amount, address sender) = abi.decode( + context.revertMessage, + (address, uint256, address) + ); + _mint(sender, amount); + emit TokenTransferAborted(sender, amount); + } } diff --git a/contracts/token/package.json b/contracts/token/package.json index 3931749..cf7f8ac 100644 --- a/contracts/token/package.json +++ b/contracts/token/package.json @@ -20,15 +20,15 @@ "@nomicfoundation/hardhat-toolbox": "^2.0.0", "@nomiclabs/hardhat-ethers": "^2.0.0", "@nomiclabs/hardhat-etherscan": "^3.0.0", - "@typechain/ethers-v5": "^10.1.0", "@openzeppelin/hardhat-upgrades": "1.28.0", + "@typechain/ethers-v5": "^10.1.0", "@typechain/hardhat": "^6.1.2", "@types/chai": "^4.2.0", "@types/mocha": ">=9.1.0", "@types/node": ">=12.0.0", "@typescript-eslint/eslint-plugin": "^5.59.9", "@typescript-eslint/parser": "^5.59.9", - "@zetachain/localnet": "4.0.0-rc6", + "@zetachain/localnet": "5.0.0-rc1", "@zetachain/toolkit": "13.0.0-rc8", "axios": "^1.3.6", "chai": "^4.2.0", @@ -59,4 +59,4 @@ "@solana/web3.js": "^1.95.2", "@zetachain/protocol-contracts": "11.0.0-rc3" } -} \ No newline at end of file +} diff --git a/contracts/token/yarn.lock b/contracts/token/yarn.lock index 820a341..2298dca 100644 --- a/contracts/token/yarn.lock +++ b/contracts/token/yarn.lock @@ -2536,15 +2536,15 @@ typescript "5.5.4" zod "3.22.4" -"@zetachain/localnet@4.0.0-rc6": - version "4.0.0-rc6" - resolved "https://registry.yarnpkg.com/@zetachain/localnet/-/localnet-4.0.0-rc6.tgz#9b36f5ab0e8fac766d63cfca4b1cab6a263bdd5d" - integrity sha512-DGKspMAJZLUrgNirc3NzFYg9jRfaOyuF5ePj85D93qAA//f8lOsXpmh/6Bvq/MrEscCLpVavgVP7+ePy4KJ2Fw== +"@zetachain/localnet@5.0.0-rc1": + version "5.0.0-rc1" + resolved "https://registry.yarnpkg.com/@zetachain/localnet/-/localnet-5.0.0-rc1.tgz#920803084156a7b86fe763541b83a24833fb54d6" + integrity sha512-HHQ01dp9fxoA8WBBEXLmh5p/GMqVdd/0oYkdzzFZQ52UNXi+ucc4B/wdpBlKwub06r6Q8edPXiPfOjGPFthcMQ== dependencies: "@inquirer/prompts" "^5.5.0" "@uniswap/v2-core" "^1.0.1" "@uniswap/v2-periphery" "^1.1.0-beta.0" - "@zetachain/protocol-contracts" "11.0.0-rc3" + "@zetachain/protocol-contracts" "11.0.0-rc4" ansis "^3.3.2" concurrently "^8.2.2" ethers "^6.13.2" @@ -2568,6 +2568,16 @@ "@zetachain/networks" "^10.0.0" ethers "5.6.8" +"@zetachain/protocol-contracts@11.0.0-rc4": + version "11.0.0-rc4" + resolved "https://registry.yarnpkg.com/@zetachain/protocol-contracts/-/protocol-contracts-11.0.0-rc4.tgz#2e2df98734793873e9c50629f6ec9f5eec6f9f54" + integrity sha512-7MJzEyUad7JgHucveIhtU8aaPkoMMzsfhKkh9MDMdxUzlaOmmxrQw2hi5B2b7UPxw2K9vFffkmIox6gd6c1+Yw== + dependencies: + "@openzeppelin/contracts" "^5.0.2" + "@openzeppelin/contracts-upgradeable" "^5.0.2" + "@zetachain/networks" "^10.0.0" + ethers "5.6.8" + "@zetachain/protocol-contracts@9.0.0": version "9.0.0" resolved "https://registry.yarnpkg.com/@zetachain/protocol-contracts/-/protocol-contracts-9.0.0.tgz#c20ad5da43f6f3676f31556b303d1cb4ea17357e"