-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
refactor: extracted core logic into an abstract UniversalNFTCore
contract
#36
base: main
Are you sure you want to change the base?
Conversation
📝 WalkthroughWalkthroughThe pull request introduces a comprehensive refactoring of the NFT and token contracts across EVM and ZetaChain platforms. The primary focus is on upgrading to OpenZeppelin Contracts version 5.0.0, streamlining cross-chain functionality, and introducing new core abstract contracts ( Changes
Sequence DiagramsequenceDiagram
participant Owner
participant UniversalNFT
participant Gateway
Owner->>UniversalNFT: initialize(owner, name, symbol)
Owner->>UniversalNFT: safeMint(recipient, tokenId)
alt Cross-Chain Transfer
Owner->>UniversalNFT: transferCrossChain(tokenId, receiver, destination)
UniversalNFT->>Gateway: Send transfer message
Gateway->>UniversalNFT: Confirm transfer
end
alt Pause/Unpause
Owner->>UniversalNFT: pause()
Owner->>UniversalNFT: unpause()
end
The sequence diagram illustrates the key interactions within the Tip CodeRabbit's docstrings feature is now available as part of our Early Access Program! Simply use the command Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
function transferCrossChain( | ||
uint256 tokenId, | ||
address receiver, | ||
address destination | ||
) external payable virtual { | ||
if (receiver == address(0)) revert InvalidAddress(); | ||
|
||
string memory uri = tokenURI(tokenId); | ||
|
||
_burn(tokenId); | ||
|
||
bytes memory message = abi.encode( | ||
destination, | ||
receiver, | ||
tokenId, | ||
uri, | ||
msg.sender | ||
); | ||
|
||
if (destination == address(0)) { | ||
gateway.call( | ||
universal, | ||
message, | ||
RevertOptions(address(this), false, address(0), message, 0) | ||
); | ||
} else { | ||
gateway.depositAndCall{value: msg.value}( | ||
universal, | ||
message, | ||
RevertOptions( | ||
address(this), | ||
true, | ||
address(0), | ||
abi.encode(receiver, tokenId, uri, msg.sender), | ||
gasLimitAmount | ||
) | ||
); | ||
} | ||
|
||
emit TokenTransfer(destination, receiver, tokenId, uri); | ||
} |
Check notice
Code scanning / Slither
Reentrancy vulnerabilities Low
External calls:
- gateway.call(universal,message,RevertOptions(address(this),false,address(0),message,0))
- gateway.depositAndCall{value: msg.value}(universal,message,RevertOptions(address(this),true,address(0),abi.encode(receiver,tokenId,uri,msg.sender),gasLimitAmount))
External calls sending eth:
- gateway.depositAndCall{value: msg.value}(universal,message,RevertOptions(address(this),true,address(0),abi.encode(receiver,tokenId,uri,msg.sender),gasLimitAmount))
Event emitted after the call(s):
- TokenTransfer(destination,receiver,tokenId,uri)
function onCall( | ||
MessageContext calldata context, | ||
bytes calldata message | ||
) external payable onlyGateway returns (bytes4) { | ||
if (context.sender != universal) revert Unauthorized(); | ||
|
||
( | ||
address receiver, | ||
uint256 tokenId, | ||
string memory uri, | ||
uint256 gasAmount, | ||
address sender | ||
) = abi.decode(message, (address, uint256, string, uint256, address)); | ||
|
||
_safeMint(receiver, tokenId); | ||
_setTokenURI(tokenId, uri); | ||
if (gasAmount > 0) { | ||
if (sender == address(0)) revert InvalidAddress(); | ||
(bool success, ) = payable(sender).call{value: gasAmount}(""); | ||
if (!success) revert GasTokenTransferFailed(); | ||
} | ||
emit TokenTransferReceived(receiver, tokenId, uri); | ||
return ""; | ||
} |
Check warning
Code scanning / Slither
Low-level calls Warning
function __UniversalNFTCore_init( | ||
address gatewayAddress, | ||
address universalAddress, | ||
uint256 gas | ||
) internal { | ||
if (gatewayAddress == address(0)) revert InvalidAddress(); | ||
if (universalAddress == address(0)) revert InvalidAddress(); | ||
if (gas == 0) revert InvalidGasLimit(); | ||
gateway = GatewayEVM(gatewayAddress); | ||
universal = universalAddress; | ||
gasLimitAmount = gas; | ||
} |
Check warning
Code scanning / Slither
Conformance to Solidity naming conventions Warning
function __UniversalNFTCore_init( | ||
address gatewayAddress, | ||
uint256 gas, | ||
address uniswapRouterAddress | ||
) internal { | ||
if (gatewayAddress == address(0) || uniswapRouterAddress == address(0)) | ||
revert InvalidAddress(); | ||
if (gas == 0) revert InvalidGasLimit(); | ||
gateway = GatewayZEVM(payable(gatewayAddress)); | ||
uniswapRouter = uniswapRouterAddress; | ||
gasLimitAmount = gas; | ||
} |
Check warning
Code scanning / Slither
Conformance to Solidity naming conventions Warning
function transferCrossChain( | ||
uint256 tokenId, | ||
address receiver, | ||
address destination | ||
) public payable { | ||
if (msg.value == 0) revert ZeroMsgValue(); | ||
if (receiver == address(0)) revert InvalidAddress(); | ||
string memory uri = tokenURI(tokenId); | ||
_burn(tokenId); | ||
|
||
(address gasZRC20, uint256 gasFee) = IZRC20(destination) | ||
.withdrawGasFeeWithGasLimit(gasLimitAmount); | ||
if (destination != gasZRC20) revert InvalidAddress(); | ||
|
||
address WZETA = gateway.zetaToken(); | ||
|
||
IWETH9(WZETA).deposit{value: msg.value}(); | ||
IWETH9(WZETA).approve(uniswapRouter, msg.value); | ||
|
||
uint256 out = SwapHelperLib.swapTokensForExactTokens( | ||
uniswapRouter, | ||
WZETA, | ||
gasFee, | ||
gasZRC20, | ||
msg.value | ||
); | ||
|
||
uint256 remaining = msg.value - out; | ||
|
||
if (remaining > 0) { | ||
IWETH9(WZETA).withdraw(remaining); | ||
(bool success, ) = msg.sender.call{value: remaining}(""); | ||
if (!success) revert TransferFailed(); | ||
} | ||
|
||
bytes memory message = abi.encode( | ||
receiver, | ||
tokenId, | ||
uri, | ||
0, | ||
msg.sender | ||
); | ||
CallOptions memory callOptions = CallOptions(gasLimitAmount, false); | ||
|
||
RevertOptions memory revertOptions = RevertOptions( | ||
address(this), | ||
true, | ||
address(0), | ||
abi.encode(tokenId, uri, msg.sender), | ||
gasLimitAmount | ||
); | ||
|
||
IZRC20(gasZRC20).approve(address(gateway), gasFee); | ||
gateway.call( | ||
abi.encodePacked(connected[destination]), | ||
destination, | ||
message, | ||
callOptions, | ||
revertOptions | ||
); | ||
|
||
emit TokenTransfer(receiver, destination, tokenId, uri); | ||
} |
Check warning
Code scanning / Slither
Unused return Medium
function transferCrossChain( | ||
uint256 tokenId, | ||
address receiver, | ||
address destination | ||
) public payable { | ||
if (msg.value == 0) revert ZeroMsgValue(); | ||
if (receiver == address(0)) revert InvalidAddress(); | ||
string memory uri = tokenURI(tokenId); | ||
_burn(tokenId); | ||
|
||
(address gasZRC20, uint256 gasFee) = IZRC20(destination) | ||
.withdrawGasFeeWithGasLimit(gasLimitAmount); | ||
if (destination != gasZRC20) revert InvalidAddress(); | ||
|
||
address WZETA = gateway.zetaToken(); | ||
|
||
IWETH9(WZETA).deposit{value: msg.value}(); | ||
IWETH9(WZETA).approve(uniswapRouter, msg.value); | ||
|
||
uint256 out = SwapHelperLib.swapTokensForExactTokens( | ||
uniswapRouter, | ||
WZETA, | ||
gasFee, | ||
gasZRC20, | ||
msg.value | ||
); | ||
|
||
uint256 remaining = msg.value - out; | ||
|
||
if (remaining > 0) { | ||
IWETH9(WZETA).withdraw(remaining); | ||
(bool success, ) = msg.sender.call{value: remaining}(""); | ||
if (!success) revert TransferFailed(); | ||
} | ||
|
||
bytes memory message = abi.encode( | ||
receiver, | ||
tokenId, | ||
uri, | ||
0, | ||
msg.sender | ||
); | ||
CallOptions memory callOptions = CallOptions(gasLimitAmount, false); | ||
|
||
RevertOptions memory revertOptions = RevertOptions( | ||
address(this), | ||
true, | ||
address(0), | ||
abi.encode(tokenId, uri, msg.sender), | ||
gasLimitAmount | ||
); | ||
|
||
IZRC20(gasZRC20).approve(address(gateway), gasFee); | ||
gateway.call( | ||
abi.encodePacked(connected[destination]), | ||
destination, | ||
message, | ||
callOptions, | ||
revertOptions | ||
); | ||
|
||
emit TokenTransfer(receiver, destination, tokenId, uri); | ||
} |
Check warning
Code scanning / Slither
Unused return Medium
function transferCrossChain( | ||
uint256 tokenId, | ||
address receiver, | ||
address destination | ||
) public payable { | ||
if (msg.value == 0) revert ZeroMsgValue(); | ||
if (receiver == address(0)) revert InvalidAddress(); | ||
string memory uri = tokenURI(tokenId); | ||
_burn(tokenId); | ||
|
||
(address gasZRC20, uint256 gasFee) = IZRC20(destination) | ||
.withdrawGasFeeWithGasLimit(gasLimitAmount); | ||
if (destination != gasZRC20) revert InvalidAddress(); | ||
|
||
address WZETA = gateway.zetaToken(); | ||
|
||
IWETH9(WZETA).deposit{value: msg.value}(); | ||
IWETH9(WZETA).approve(uniswapRouter, msg.value); | ||
|
||
uint256 out = SwapHelperLib.swapTokensForExactTokens( | ||
uniswapRouter, | ||
WZETA, | ||
gasFee, | ||
gasZRC20, | ||
msg.value | ||
); | ||
|
||
uint256 remaining = msg.value - out; | ||
|
||
if (remaining > 0) { | ||
IWETH9(WZETA).withdraw(remaining); | ||
(bool success, ) = msg.sender.call{value: remaining}(""); | ||
if (!success) revert TransferFailed(); | ||
} | ||
|
||
bytes memory message = abi.encode( | ||
receiver, | ||
tokenId, | ||
uri, | ||
0, | ||
msg.sender | ||
); | ||
CallOptions memory callOptions = CallOptions(gasLimitAmount, false); | ||
|
||
RevertOptions memory revertOptions = RevertOptions( | ||
address(this), | ||
true, | ||
address(0), | ||
abi.encode(tokenId, uri, msg.sender), | ||
gasLimitAmount | ||
); | ||
|
||
IZRC20(gasZRC20).approve(address(gateway), gasFee); | ||
gateway.call( | ||
abi.encodePacked(connected[destination]), | ||
destination, | ||
message, | ||
callOptions, | ||
revertOptions | ||
); | ||
|
||
emit TokenTransfer(receiver, destination, tokenId, uri); | ||
} |
Check notice
Code scanning / Slither
Reentrancy vulnerabilities Low
External calls:
- IWETH9(WZETA).deposit{value: msg.value}()
- IWETH9(WZETA).approve(uniswapRouter,msg.value)
- out = SwapHelperLib.swapTokensForExactTokens(uniswapRouter,WZETA,gasFee,gasZRC20,msg.value)
- IWETH9(WZETA).withdraw(remaining)
- (success,None) = msg.sender.call{value: remaining}()
- IZRC20(gasZRC20).approve(address(gateway),gasFee)
- gateway.call(abi.encodePacked(connected[destination]),destination,message,callOptions,revertOptions)
External calls sending eth:
- IWETH9(WZETA).deposit{value: msg.value}()
- (success,None) = msg.sender.call{value: remaining}()
Event emitted after the call(s):
- TokenTransfer(receiver,destination,tokenId,uri)
function transferCrossChain( | ||
uint256 tokenId, | ||
address receiver, | ||
address destination | ||
) public payable { | ||
if (msg.value == 0) revert ZeroMsgValue(); | ||
if (receiver == address(0)) revert InvalidAddress(); | ||
string memory uri = tokenURI(tokenId); | ||
_burn(tokenId); | ||
|
||
(address gasZRC20, uint256 gasFee) = IZRC20(destination) | ||
.withdrawGasFeeWithGasLimit(gasLimitAmount); | ||
if (destination != gasZRC20) revert InvalidAddress(); | ||
|
||
address WZETA = gateway.zetaToken(); | ||
|
||
IWETH9(WZETA).deposit{value: msg.value}(); | ||
IWETH9(WZETA).approve(uniswapRouter, msg.value); | ||
|
||
uint256 out = SwapHelperLib.swapTokensForExactTokens( | ||
uniswapRouter, | ||
WZETA, | ||
gasFee, | ||
gasZRC20, | ||
msg.value | ||
); | ||
|
||
uint256 remaining = msg.value - out; | ||
|
||
if (remaining > 0) { | ||
IWETH9(WZETA).withdraw(remaining); | ||
(bool success, ) = msg.sender.call{value: remaining}(""); | ||
if (!success) revert TransferFailed(); | ||
} | ||
|
||
bytes memory message = abi.encode( | ||
receiver, | ||
tokenId, | ||
uri, | ||
0, | ||
msg.sender | ||
); | ||
CallOptions memory callOptions = CallOptions(gasLimitAmount, false); | ||
|
||
RevertOptions memory revertOptions = RevertOptions( | ||
address(this), | ||
true, | ||
address(0), | ||
abi.encode(tokenId, uri, msg.sender), | ||
gasLimitAmount | ||
); | ||
|
||
IZRC20(gasZRC20).approve(address(gateway), gasFee); | ||
gateway.call( | ||
abi.encodePacked(connected[destination]), | ||
destination, | ||
message, | ||
callOptions, | ||
revertOptions | ||
); | ||
|
||
emit TokenTransfer(receiver, destination, tokenId, uri); | ||
} |
Check warning
Code scanning / Slither
Low-level calls Warning
UniversalNFTCore
contract
function __UniversalTokenCore_init( | ||
address gatewayAddress, | ||
address universalAddress, | ||
uint256 gas | ||
) internal { | ||
if (gatewayAddress == address(0)) revert InvalidAddress(); | ||
if (universalAddress == address(0)) revert InvalidAddress(); | ||
if (gas == 0) revert InvalidGasLimit(); | ||
gateway = GatewayEVM(gatewayAddress); | ||
universal = universalAddress; | ||
gasLimitAmount = gas; | ||
} |
Check warning
Code scanning / Slither
Conformance to Solidity naming conventions Warning
function transferCrossChain( | ||
address destination, | ||
address receiver, | ||
uint256 amount | ||
) external payable { | ||
if (receiver == address(0)) revert InvalidAddress(); | ||
_burn(msg.sender, amount); | ||
|
||
bytes memory message = abi.encode( | ||
destination, | ||
receiver, | ||
amount, | ||
msg.sender | ||
); | ||
if (destination == address(0)) { | ||
gateway.call( | ||
universal, | ||
message, | ||
RevertOptions(address(this), false, address(0), message, 0) | ||
); | ||
} else { | ||
gateway.depositAndCall{value: msg.value}( | ||
universal, | ||
message, | ||
RevertOptions( | ||
address(this), | ||
true, | ||
address(0), | ||
abi.encode(amount, msg.sender), | ||
gasLimitAmount | ||
) | ||
); | ||
} | ||
|
||
emit TokenTransfer(destination, receiver, amount); | ||
} |
Check notice
Code scanning / Slither
Reentrancy vulnerabilities Low
External calls:
- gateway.call(universal,message,RevertOptions(address(this),false,address(0),message,0))
- gateway.depositAndCall{value: msg.value}(universal,message,RevertOptions(address(this),true,address(0),abi.encode(amount,msg.sender),gasLimitAmount))
External calls sending eth:
- gateway.depositAndCall{value: msg.value}(universal,message,RevertOptions(address(this),true,address(0),abi.encode(amount,msg.sender),gasLimitAmount))
Event emitted after the call(s):
- TokenTransfer(destination,receiver,amount)
function onCall( | ||
MessageContext calldata context, | ||
bytes calldata message | ||
) external payable onlyGateway returns (bytes4) { | ||
if (context.sender != universal) revert Unauthorized(); | ||
( | ||
address receiver, | ||
uint256 amount, | ||
uint256 gasAmount, | ||
address sender | ||
) = abi.decode(message, (address, uint256, uint256, address)); | ||
_mint(receiver, amount); | ||
if (gasAmount > 0) { | ||
if (sender == address(0)) revert InvalidAddress(); | ||
(bool success, ) = payable(sender).call{value: amount}(""); | ||
if (!success) revert GasTokenTransferFailed(); | ||
} | ||
emit TokenTransferReceived(receiver, amount); | ||
return ""; | ||
} |
Check notice
Code scanning / Slither
Reentrancy vulnerabilities Low
External calls:
- (success,None) = address(sender).call{value: amount}()
Event emitted after the call(s):
- TokenTransferReceived(receiver,amount)
function onCall( | ||
MessageContext calldata context, | ||
bytes calldata message | ||
) external payable onlyGateway returns (bytes4) { | ||
if (context.sender != universal) revert Unauthorized(); | ||
( | ||
address receiver, | ||
uint256 amount, | ||
uint256 gasAmount, | ||
address sender | ||
) = abi.decode(message, (address, uint256, uint256, address)); | ||
_mint(receiver, amount); | ||
if (gasAmount > 0) { | ||
if (sender == address(0)) revert InvalidAddress(); | ||
(bool success, ) = payable(sender).call{value: amount}(""); | ||
if (!success) revert GasTokenTransferFailed(); | ||
} | ||
emit TokenTransferReceived(receiver, amount); | ||
return ""; | ||
} |
Check warning
Code scanning / Slither
Low-level calls Warning
function __UniversalTokenCore_init( | ||
address gatewayAddress, | ||
uint256 gas, | ||
address uniswapRouterAddress | ||
) internal { | ||
if (gatewayAddress == address(0) || uniswapRouterAddress == address(0)) | ||
revert InvalidAddress(); | ||
if (gas == 0) revert InvalidGasLimit(); | ||
gateway = GatewayZEVM(payable(gatewayAddress)); | ||
uniswapRouter = uniswapRouterAddress; | ||
gasLimitAmount = gas; | ||
} |
Check warning
Code scanning / Slither
Conformance to Solidity naming conventions Warning
function transferCrossChain( | ||
address destination, | ||
address receiver, | ||
uint256 amount | ||
) public payable { | ||
if (msg.value == 0) revert ZeroMsgValue(); | ||
if (receiver == address(0)) revert InvalidAddress(); | ||
_burn(msg.sender, amount); | ||
|
||
(address gasZRC20, uint256 gasFee) = IZRC20(destination) | ||
.withdrawGasFeeWithGasLimit(gasLimitAmount); | ||
if (destination != gasZRC20) revert InvalidAddress(); | ||
|
||
address WZETA = gateway.zetaToken(); | ||
|
||
IWETH9(WZETA).deposit{value: msg.value}(); | ||
IWETH9(WZETA).approve(uniswapRouter, msg.value); | ||
|
||
uint256 out = SwapHelperLib.swapTokensForExactTokens( | ||
uniswapRouter, | ||
WZETA, | ||
gasFee, | ||
gasZRC20, | ||
msg.value | ||
); | ||
|
||
uint256 remaining = msg.value - out; | ||
|
||
if (remaining > 0) { | ||
IWETH9(WZETA).withdraw(remaining); | ||
(bool success, ) = msg.sender.call{value: remaining}(""); | ||
if (!success) revert TransferFailed(); | ||
} | ||
|
||
bytes memory message = abi.encode(receiver, amount, 0, msg.sender); | ||
|
||
CallOptions memory callOptions = CallOptions(gasLimitAmount, false); | ||
|
||
RevertOptions memory revertOptions = RevertOptions( | ||
address(this), | ||
true, | ||
address(0), | ||
abi.encode(amount, msg.sender), | ||
gasLimitAmount | ||
); | ||
|
||
IZRC20(gasZRC20).approve(address(gateway), gasFee); | ||
gateway.call( | ||
abi.encodePacked(connected[destination]), | ||
destination, | ||
message, | ||
callOptions, | ||
revertOptions | ||
); | ||
emit TokenTransfer(destination, receiver, amount); | ||
} |
Check warning
Code scanning / Slither
Unused return Medium
function transferCrossChain( | ||
address destination, | ||
address receiver, | ||
uint256 amount | ||
) public payable { | ||
if (msg.value == 0) revert ZeroMsgValue(); | ||
if (receiver == address(0)) revert InvalidAddress(); | ||
_burn(msg.sender, amount); | ||
|
||
(address gasZRC20, uint256 gasFee) = IZRC20(destination) | ||
.withdrawGasFeeWithGasLimit(gasLimitAmount); | ||
if (destination != gasZRC20) revert InvalidAddress(); | ||
|
||
address WZETA = gateway.zetaToken(); | ||
|
||
IWETH9(WZETA).deposit{value: msg.value}(); | ||
IWETH9(WZETA).approve(uniswapRouter, msg.value); | ||
|
||
uint256 out = SwapHelperLib.swapTokensForExactTokens( | ||
uniswapRouter, | ||
WZETA, | ||
gasFee, | ||
gasZRC20, | ||
msg.value | ||
); | ||
|
||
uint256 remaining = msg.value - out; | ||
|
||
if (remaining > 0) { | ||
IWETH9(WZETA).withdraw(remaining); | ||
(bool success, ) = msg.sender.call{value: remaining}(""); | ||
if (!success) revert TransferFailed(); | ||
} | ||
|
||
bytes memory message = abi.encode(receiver, amount, 0, msg.sender); | ||
|
||
CallOptions memory callOptions = CallOptions(gasLimitAmount, false); | ||
|
||
RevertOptions memory revertOptions = RevertOptions( | ||
address(this), | ||
true, | ||
address(0), | ||
abi.encode(amount, msg.sender), | ||
gasLimitAmount | ||
); | ||
|
||
IZRC20(gasZRC20).approve(address(gateway), gasFee); | ||
gateway.call( | ||
abi.encodePacked(connected[destination]), | ||
destination, | ||
message, | ||
callOptions, | ||
revertOptions | ||
); | ||
emit TokenTransfer(destination, receiver, amount); | ||
} |
Check warning
Code scanning / Slither
Unused return Medium
function transferCrossChain( | ||
address destination, | ||
address receiver, | ||
uint256 amount | ||
) public payable { | ||
if (msg.value == 0) revert ZeroMsgValue(); | ||
if (receiver == address(0)) revert InvalidAddress(); | ||
_burn(msg.sender, amount); | ||
|
||
(address gasZRC20, uint256 gasFee) = IZRC20(destination) | ||
.withdrawGasFeeWithGasLimit(gasLimitAmount); | ||
if (destination != gasZRC20) revert InvalidAddress(); | ||
|
||
address WZETA = gateway.zetaToken(); | ||
|
||
IWETH9(WZETA).deposit{value: msg.value}(); | ||
IWETH9(WZETA).approve(uniswapRouter, msg.value); | ||
|
||
uint256 out = SwapHelperLib.swapTokensForExactTokens( | ||
uniswapRouter, | ||
WZETA, | ||
gasFee, | ||
gasZRC20, | ||
msg.value | ||
); | ||
|
||
uint256 remaining = msg.value - out; | ||
|
||
if (remaining > 0) { | ||
IWETH9(WZETA).withdraw(remaining); | ||
(bool success, ) = msg.sender.call{value: remaining}(""); | ||
if (!success) revert TransferFailed(); | ||
} | ||
|
||
bytes memory message = abi.encode(receiver, amount, 0, msg.sender); | ||
|
||
CallOptions memory callOptions = CallOptions(gasLimitAmount, false); | ||
|
||
RevertOptions memory revertOptions = RevertOptions( | ||
address(this), | ||
true, | ||
address(0), | ||
abi.encode(amount, msg.sender), | ||
gasLimitAmount | ||
); | ||
|
||
IZRC20(gasZRC20).approve(address(gateway), gasFee); | ||
gateway.call( | ||
abi.encodePacked(connected[destination]), | ||
destination, | ||
message, | ||
callOptions, | ||
revertOptions | ||
); | ||
emit TokenTransfer(destination, receiver, amount); | ||
} |
Check notice
Code scanning / Slither
Reentrancy vulnerabilities Low
External calls:
- IWETH9(WZETA).deposit{value: msg.value}()
- IWETH9(WZETA).approve(uniswapRouter,msg.value)
- out = SwapHelperLib.swapTokensForExactTokens(uniswapRouter,WZETA,gasFee,gasZRC20,msg.value)
- IWETH9(WZETA).withdraw(remaining)
- (success,None) = msg.sender.call{value: remaining}()
- IZRC20(gasZRC20).approve(address(gateway),gasFee)
- gateway.call(abi.encodePacked(connected[destination]),destination,message,callOptions,revertOptions)
External calls sending eth:
- IWETH9(WZETA).deposit{value: msg.value}()
- (success,None) = msg.sender.call{value: remaining}()
Event emitted after the call(s):
- TokenTransfer(destination,receiver,amount)
function transferCrossChain( | ||
address destination, | ||
address receiver, | ||
uint256 amount | ||
) public payable { | ||
if (msg.value == 0) revert ZeroMsgValue(); | ||
if (receiver == address(0)) revert InvalidAddress(); | ||
_burn(msg.sender, amount); | ||
|
||
(address gasZRC20, uint256 gasFee) = IZRC20(destination) | ||
.withdrawGasFeeWithGasLimit(gasLimitAmount); | ||
if (destination != gasZRC20) revert InvalidAddress(); | ||
|
||
address WZETA = gateway.zetaToken(); | ||
|
||
IWETH9(WZETA).deposit{value: msg.value}(); | ||
IWETH9(WZETA).approve(uniswapRouter, msg.value); | ||
|
||
uint256 out = SwapHelperLib.swapTokensForExactTokens( | ||
uniswapRouter, | ||
WZETA, | ||
gasFee, | ||
gasZRC20, | ||
msg.value | ||
); | ||
|
||
uint256 remaining = msg.value - out; | ||
|
||
if (remaining > 0) { | ||
IWETH9(WZETA).withdraw(remaining); | ||
(bool success, ) = msg.sender.call{value: remaining}(""); | ||
if (!success) revert TransferFailed(); | ||
} | ||
|
||
bytes memory message = abi.encode(receiver, amount, 0, msg.sender); | ||
|
||
CallOptions memory callOptions = CallOptions(gasLimitAmount, false); | ||
|
||
RevertOptions memory revertOptions = RevertOptions( | ||
address(this), | ||
true, | ||
address(0), | ||
abi.encode(amount, msg.sender), | ||
gasLimitAmount | ||
); | ||
|
||
IZRC20(gasZRC20).approve(address(gateway), gasFee); | ||
gateway.call( | ||
abi.encodePacked(connected[destination]), | ||
destination, | ||
message, | ||
callOptions, | ||
revertOptions | ||
); | ||
emit TokenTransfer(destination, receiver, amount); | ||
} |
Check warning
Code scanning / Slither
Low-level calls Warning
function onCall( | ||
MessageContext calldata context, | ||
address zrc20, | ||
uint256 amount, | ||
bytes calldata message | ||
) external override onlyGateway { | ||
if (context.sender != connected[zrc20]) revert Unauthorized(); | ||
( | ||
address destination, | ||
address receiver, | ||
uint256 tokenAmount, | ||
address sender | ||
) = abi.decode(message, (address, address, uint256, address)); | ||
if (destination == address(0)) { | ||
_mint(receiver, tokenAmount); | ||
} else { | ||
(address gasZRC20, uint256 gasFee) = IZRC20(destination) | ||
.withdrawGasFeeWithGasLimit(gasLimitAmount); | ||
if (destination != gasZRC20) revert InvalidAddress(); | ||
uint256 out = SwapHelperLib.swapExactTokensForTokens( | ||
uniswapRouter, | ||
zrc20, | ||
amount, | ||
destination, | ||
0 | ||
); | ||
if (!IZRC20(destination).approve(address(gateway), out)) { | ||
revert ApproveFailed(); | ||
} | ||
gateway.withdrawAndCall( | ||
abi.encodePacked(connected[destination]), | ||
out - gasFee, | ||
destination, | ||
abi.encode(receiver, tokenAmount, out - gasFee, sender), | ||
CallOptions(gasLimitAmount, false), | ||
RevertOptions( | ||
address(this), | ||
true, | ||
address(0), | ||
abi.encode(tokenAmount, sender), | ||
0 | ||
) | ||
); | ||
} | ||
emit TokenTransferToDestination(destination, receiver, amount); | ||
} |
Check notice
Code scanning / Slither
Reentrancy vulnerabilities Low
External calls:
- out = SwapHelperLib.swapExactTokensForTokens(uniswapRouter,zrc20,amount,destination,0)
- ! IZRC20(destination).approve(address(gateway),out)
- gateway.withdrawAndCall(abi.encodePacked(connected[destination]),out - gasFee,destination,abi.encode(receiver,tokenAmount,out - gasFee,sender),CallOptions(gasLimitAmount,false),RevertOptions(address(this),true,address(0),abi.encode(tokenAmount,sender),0))
Event emitted after the call(s):
- TokenTransferToDestination(destination,receiver,amount)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (13)
contracts/token/contracts/evm/UniversalToken.sol (1)
50-50
: Consider access controls for minting.
Currently, only the owner can mint. This is fine for certain scenarios but consider if there's a need for role-based access controls (e.g.MINTER_ROLE
) for flexible future usage.contracts/token/contracts/zetachain/UniversalToken.sol (1)
9-9
: Remove duplicate import.
ERC20BurnableUpgradeable
appears to be imported twice (line 9 and line 9 repeated). Ensure each import is intentional, or remove duplicates to declutter.- import {ERC20BurnableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20BurnableUpgradeable.sol"; - import {ERC20BurnableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20BurnableUpgradeable.sol";contracts/nft/contracts/evm/UniversalNFT.sol (2)
2-2
: Include rationale for version references in a comment.
Mentioning compatibility with OpenZeppelin Contracts ^5.0.0 is helpful. Consider adding which features from v5.0.0 are being leveraged.
44-44
: Initialize pausable last for clarity.
__ERC721Pausable_init()
is placed with other inits. Consider grouping related initializers clearly for maintainability.contracts/token/contracts/evm/UniversalTokenCore.sol (2)
4-10
: Imports are well-organized.
Separating protocol-contract imports, OpenZeppelin imports, and local references (UniversalTokenEvents
) is good for readability.
91-110
:onCall
callback
Logic properly checkscontext.sender == universal
, then mints tokens and attempts to forward gas fees. Watch for re-entrancy on external call topayable(sender).call{value:amount}("")
.// Potentially use the OpenZeppelin ReentrancyGuard: +import "@openzeppelin/contracts/security/ReentrancyGuard.sol" ... function onCall(...) external payable onlyGateway nonReentrant returns (bytes4) { ... }
contracts/nft/contracts/evm/UniversalNFTCore.sol (2)
36-40
:setUniversal
Function
This function updates theuniversal
address and emits an event. Consider adding more checks (e.g., code size of the address) if you expect only contract addresses.
63-103
: Potential Re-Entrancy Risks When Transferring Cross-Chain
The code performs multiple external calls (e.g.,gateway.call
/gateway.depositAndCall
) and sends ETH. Although you are using short flows and burning the token prior to cross-chain messaging, carefully review the call order and guard your contract if you add more state changes later.contracts/token/contracts/zetachain/UniversalTokenCore.sol (3)
35-38
:onlyGateway
Modifier
Similar to the NFT core, this gating is consistent. Document or reference in the code comments that cross-chain messages must come from the official gateway to avoid confusion for new integrators.
53-56
:setGasLimit
Matches the pattern from the NFT core. Confirm that gas usage in the cross-chain calls is consistent with this user-defined field. Also consider event emission for clarity.
66-121
:transferCrossChain
You handle zero-value checks (ZeroMsgValue
), burn tokens, handle gas fee, deposit WZETA, and swap to pay cross-chain costs. This approach is thorough. Carefully track leftover WZETA logic in future expansions to avoid dust amounts that remain locked.contracts/nft/contracts/zetachain/UniversalNFTCore.sol (1)
66-128
:transferCrossChain
and Re-Entrancy
Calls likeIWETH9(WZETA).approve()
,IZRC20(gasZRC20).approve()
and subsequentgateway.call()
can be re-entrancy vectors if new storage writes are introduced in the future. An extranonReentrant
guard or pattern might be advisable for safety. Also, ignore return values from the approvals with caution—if any approval fails, it should revert.contracts/nft/tasks/transfer.ts (1)
45-45
: JSON Output
The structured JSON includes the relevant data. Consider also including gas costs or cross-chain fees in this output for debugging or cost analysis.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
contracts/nft/contracts/evm/UniversalNFT.sol
(5 hunks)contracts/nft/contracts/evm/UniversalNFTCore.sol
(1 hunks)contracts/nft/contracts/zetachain/UniversalNFT.sol
(5 hunks)contracts/nft/contracts/zetachain/UniversalNFTCore.sol
(1 hunks)contracts/nft/scripts/localnet.sh
(1 hunks)contracts/nft/tasks/transfer.ts
(5 hunks)contracts/token/contracts/evm/UniversalToken.sol
(2 hunks)contracts/token/contracts/evm/UniversalTokenCore.sol
(1 hunks)contracts/token/contracts/zetachain/UniversalToken.sol
(4 hunks)contracts/token/contracts/zetachain/UniversalTokenCore.sol
(1 hunks)
🔇 Additional comments (72)
contracts/token/contracts/evm/UniversalToken.sol (6)
11-11
: Use explicit interface references for clarity.
Importing UniversalTokenCore.sol
is necessary for the new inheritance model. Ensure code references to its functions or events are explicitly documented to maintain clarity around cross-chain functionalities.
20-20
: Inherited abstract contract usage is appropriate.
The shift to inheriting UniversalTokenCore
consolidates cross-chain logic in a dedicated contract and simplifies this file. The approach aligns with DRY principles.
42-43
: Protecting pause functionality.
The pause()
function is restricted to onlyOwner
, which is generally acceptable. Ensure any operational team using multi-sig or governance structure also has access, if needed.
46-47
: Unpausing logic is correct.
Similarly, unpausing is restricted to onlyOwner
. This is consistent with how pausable patterns are typically implemented.
58-58
: Override _update
usage is consistent.
Overriding _update
to combine ERC20Upgradeable
and ERC20PausableUpgradeable
logic is straightforward. No issues spotted.
36-39
: Check upgrade implications of combined initializers.
Calling multiple initializers (__ERC20Pausable_init()
, __UniversalTokenCore_init()
) is valid, but ensure they don’t conflict or double-initialize state. Consider verifying each newly inherited member’s storage alignment.
✅ Verification successful
Storage layout appears safe for upgrades
Based on the analysis of the inheritance chain and storage variables:
-
The contract follows a proper initialization pattern with distinct initializers:
UniversalToken
inherits fromInitializable
,ERC20Upgradeable
,ERC20BurnableUpgradeable
,ERC20PausableUpgradeable
, andOwnableUpgradeable
- Each base contract uses the OpenZeppelin upgradeable pattern
- The initialization calls are ordered correctly from most basic to most specific
-
Storage layout examination shows:
- Base contracts use isolated storage patterns from OpenZeppelin's upgradeable contracts
UniversalTokenCore
defines non-conflicting storage variables (gateway
,universal
,gasLimitAmount
)- No storage collisions are detected between the inherited contracts
The initialization sequence is safe and follows upgradeable contract best practices. The storage layout maintains proper isolation between inherited contracts.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Examine the storage layout for collisions in upgraded contracts.
ast-grep --pattern $'contract $_ {\n $$$\n}' | rg "storage|slot"
Length of output: 64
Script:
#!/bin/bash
# Let's examine the inheritance chain and initializers
ast-grep --pattern 'contract $_ is $_' contracts/token/contracts/evm/UniversalToken.sol
# Check the implementation of UniversalTokenCore initialization
ast-grep --pattern 'function __UniversalTokenCore_init($$$) {
$$$
}'
# Check ERC20Pausable initialization
ast-grep --pattern 'function __ERC20Pausable_init() {
$$$
}'
# Look for storage variable declarations in the inheritance chain
rg "^\s*(uint|int|bool|address|mapping|struct|bytes|string)" contracts/token/contracts/evm/UniversalToken.sol -A 1
# Check for storage gaps in the contracts
rg "uint256\[\d+\]\s+private\s+__gap" -A 1
Length of output: 775
Script:
#!/bin/bash
# Let's check the base contracts and their inheritance
rg "contract UniversalToken" -A 5 contracts/token/contracts/evm/UniversalToken.sol
# Check UniversalTokenCore contract
rg "contract UniversalTokenCore" -A 5 -r .
# Look for storage variables in related contracts
rg "^\s*(uint|int|bool|address|mapping|struct|bytes|string)" -g "**/*Token*.sol" -g "**/*Pausable*.sol" -g "**/*Ownable*.sol" -A 1
# Check for initialize functions in the inheritance chain
ast-grep --pattern 'function initialize($$$) {
$$$
}'
Length of output: 10292
contracts/token/contracts/zetachain/UniversalToken.sol (5)
17-17
: Use consistent import formatting.
import "./UniversalTokenCore.sol";
clarifies the local path. If code is part of the same package, keep imports consistent with the rest of the codebase (relative or absolute).
26-26
: Good approach to unify cross-chain logic via UniversalTokenCore
.
Inheriting UniversalTokenCore
ensures that cross-chain features are consistently maintained in a separate module.
45-45
: Confirm compatibility of addresses in _init()
.
Ensure gatewayAddress
and uniswapRouterAddress
are valid. Although checks happen in __UniversalTokenCore_init()
, confirm no additional constraints are needed (e.g., whitelists or pre-set routers).
56-58
: mint
is well-protected.
Restricting mint rights to the owner is acceptable. For production scenarios, consider role-based or multi-sig governance.
74-74
: Optional fallback function usage.
Allowing receive() external payable {}
can be beneficial, but confirm if the contract truly needs to accept native tokens.
contracts/nft/contracts/zetachain/UniversalNFT.sol (4)
13-13
: Consistent reference to UniversalNFTCore
.
Inheriting a core module mirrors the approach used in tokens. This reduces duplication and centralizes cross-chain logic.
24-24
: Inheritance alignment is sound.
UniversalNFTCore
integration ensures feature parity and code reuse. No apparent conflicts with other inherited classes.
44-44
: Pause functionality properly initialized.
__ERC721Pausable_init()
sets up the pausable capacity. No issues seen.
48-48
: Initialize cross-chain logic in a single call.
__UniversalNFTCore_init(gatewayAddress, gas, uniswapRouterAddress)
clarifies cross-chain setup. That said, ensure thorough testing is done to avoid uninitialized storage collisions.
contracts/nft/contracts/evm/UniversalNFT.sol (14)
3-3
: Newer pragma version is acceptable.
^0.8.26
is stable unless there's a known bug. Confirm any known vulnerabilities are addressed if the contract is widely used.
6-6
: Burnable NFT extension is properly included.
This extension is a standard approach to allow NFT burning. Ensure any additional burning restrictions are in place if user minted tokens shouldn’t be burned arbitrarily.
8-8
: Pausable NFT extension is consistent.
Adding pausing offers an operational safeguard. Check if ephemeral token states are impacted by pause/unpause transitions.
10-10
: Initializable usage is standard.
Initializable
is necessary for upgradeable proxies. This is correct.
14-14
: Core functionality factorization.
UniversalNFTCore
centralizes cross-chain NFT logic. This fosters reusability across chain-specific contracts.
20-20
: Override ordering for clarity.
ERC721URIStorageUpgradeable
is placed before ERC721PausableUpgradeable
. The order is relevant for function overrides. Confirm that no overshadow of _update()
or _setTokenURI()
occurs.
25-25
: Abstract inheritance with UniversalNFTCore
.
Centralizing cross-chain operations in a single base is a good design move.
48-48
: Chain-specific parameter passing.
Passing address(this)
to _init
ensures the forward references are correct. Confirm extra trust assumptions around calling the same address.
54-54
: safeMint
gating with both onlyOwner
and whenNotPaused
Combining the two modifiers is typically best practice for controlled minting. Good approach.
67-68
: Pause function.
Only the owner can invoke pause()
. This is consistent with typical pattern usage.
71-72
: Unpause function.
Similarly, unpause()
is restricted to the owner. This is standard and correct.
75-77
: Ensure upgrade logic is tested.
_authorizeUpgrade
is limited to onlyOwner
. Confirm thorough testing or usage of test upgrade proxies before production.
109-113
: tokenURI
override order is valid.
Overriding across multiple inheritors, including UniversalNFTCore
, is consistent. Confirm that any custom token URI logic in UniversalNFTCore
is accounted for here.
127-128
: supportsInterface
override is thorough.
Aggregating interfaces from each inherited class helps ensure broad compatibility. No issues found.
contracts/token/contracts/evm/UniversalTokenCore.sol (10)
1-3
: File header and pragma usage.
Using // SPDX-License-Identifier: MIT
with ^0.8.26
is standard. This approach is recommended for clarity on licensing.
11-15
: Abstract contract with multi-inheritance.
UniversalTokenCore
extends multiple classes. This is acceptable, but confirm that each base class’s storage layouts align.
16-18
: Gateway, universal, and gas limit are clearly named.
Straightforward naming and public visibility for essential cross-chain variables. No issues found.
20-23
: Custom errors enhance revert clarity.
Defining typed errors clarifies revert reasons cost-effectively. Good practice.
25-28
: onlyGateway
guard ensures call origin.
This helps secure onCall
and onRevert
. Verify the gateway contract is properly authenticated off-chain.
30-33
: Gas limit setter with zero check.
Reverts on zero gas limit, preventing misconfiguration. This is correct.
35-39
: Universal address setter.
Ensuring non-zero address with an event is a robust approach for cross-chain usage. Good practice.
41-52
: Core initializer validations.
Ensuring non-zero addresses and non-zero gas is crucial. Contract properly references gateway = GatewayEVM(gatewayAddress)
.
112-119
: onRevert
callback
Re-mints tokens to revert a failed cross-chain transfer. This ensures user funds aren’t lost. Consider event logs for debugging cross-chain reverts. The emit TokenTransferReverted(...)
is sufficient.
120-120
: Contract end
No issues.
contracts/nft/contracts/evm/UniversalNFTCore.sol (9)
1-10
: Ensure Up-to-Date Imports and Licenses
The imports look correct and the license is set to MIT. Confirm that all imported contracts (e.g., GatewayEVM
) are at the desired versions for production.
11-20
: Inheritance Order and Access Modifiers
Your inheritance hierarchy clearly places ERC721Upgradeable
and related contracts before OwnableUpgradeable
. This is acceptable. Review whether any constructor-based logic remains in these inherited classes and ensure they do not conflict with your upgradeable initialization approach.
21-25
: Centralized State Variables
Storing gateway, universal address, and gas limit in one contract centralizes cross-chain logic. Ensure no naming conflicts with derived classes or sibling contracts that might also define similar variables.
26-29
: onlyGateway
Modifier
This implementation correctly restricts callable functions from the gateway only. Make sure to document external usage if integrators need to identify gateway addresses programmatically.
31-34
: setGasLimit
Validation
The check if (gas == 0) revert InvalidGasLimit();
helps prevent invalid input. Verify that a zero gas limit is indeed never a valid scenario.
42-53
: Initializer Naming: Not in MixedCase
This function name __UniversalNFTCore_init
triggers a prior naming-convention warning.
105-128
: onCall
Implementation
Good approach to decode the message and safely mint tokens. Confirm that all reverts or errors are handled consistently across all cross-chain callbacks to avoid inconsistent state on partial failures.
130-140
: Revert Logic
When reverting a cross-chain transfer, the contract remints the token to the original sender. Explicitly ensure that any partial state updates made before the revert scenario are also accounted for in derived contracts.
141-163
: tokenURI
and supportsInterface
Overrides
Your overrides conform to OpenZeppelin’s approach, which is good. Keep documentation updated to inform integrators about differences between local URIs and cross-chain minted URIs if that arises.
contracts/token/contracts/zetachain/UniversalTokenCore.sol (7)
1-11
: Imports & Modularity
You are combining multiple interfaces (e.g., IGatewayZEVM
, IWZETA
) and libraries (SwapHelperLib
). Continue ensuring that contract size remains manageable; otherwise, consider splitting some functionality into separate libraries.
12-19
: Contract Declaration & State Variables
Centralizing gateway
, uniswapRouter
, and gasLimitAmount
is consistent with the cross-chain approach. Ensure these do not conflict with possible inherited variables if the derived contracts expand functionality further.
26-34
: Comprehensive Error Handling
Your custom errors (e.g., TransferFailed
, Unauthorized
, etc.) provide clarity. Confirm all revert messages are consistent across the codebase so integrators can reliably detect error states.
40-51
: __UniversalTokenCore_init
Checks for gatewayAddress != address(0)
and gas != 0
are robust. If you anticipate future expansions, consider a pattern for re-initialization or versioning if your contract is upgradeable.
58-64
: setConnected
The function sets up a mapping from ZRC20 to connected contract addresses. Consider validating code size for contractAddress
if needed.
123-168
: onCall
Logic & Another Nested Swap
The contract does a nested swap for gas fees or token bridging, then calls gateway.withdrawAndCall
. Confirm that the usage of approvals is validated or at least logged for transparency, since it's granting permission to external contracts.
170-178
: onRevert
Similar to the NFT approach, you mint tokens back to the sender. This is a sensible fallback. Maintain consistent revert messages (abi.encode
) to unify the debugging process across all cross-chain calls.
contracts/nft/contracts/zetachain/UniversalNFTCore.sol (7)
1-13
: Imports and Base Structure
The contract extends UniversalContract
, ERC721Upgradeable
, ERC721URIStorageUpgradeable
, and OwnableUpgradeable
. This multi-inheritance approach is logical for cross-chain NFT use cases. Validate final bytecode size if usage grows.
14-20
: State Variables & connected
Mapping
You track gateway
, uniswapRouter
, and a connected
mapping. This parallels the approach in UniversalTokenCore
. Keep consistent naming across token and NFT cores to ease comprehension for integrators.
28-34
: Error Descriptions
The custom errors are consistent with your approach in the EVM version. Ensure mismatch scenarios (e.g., user provides a 0 address or invalid ZRC20) always revert, since partial usage could lead to unpredictability.
40-51
: Initializer: Non-MixedCase
Like the EVM version, __UniversalNFTCore_init
triggers a naming convention warning.
130-182
: onCall
Handling
Correctly decodes cross-chain messages and mints or swaps tokens as needed. Keep a watch for potential gas griefing scenarios if the contract runs out of gas partway. Document recommended gas usage for clients.
184-193
: onRevert
Recreate the NFT for the original sender. Matches earlier patterns. Make sure all reverts are consistently documented.
195-218
: ERC721 Standard Overrides
tokenURI
and supportsInterface
overrides align with standard practices. Provide clarity in documentation if any cross-chain minted URI differs from local minted URI assumptions.
contracts/nft/tasks/transfer.ts (7)
10-10
: Validation of Addresses
Nice to see you validate both args.destination
and args.revertAddress
. This proactive check is valuable for scripts.
14-17
: Approving the Contract
You approve the same contract as your args.contract
. Verify you actually need to approve the contract to transfer from itself or if another contract address is more appropriate (e.g., the universal core).
27-27
: Contract Retrieval
The script retrieves ZetaChainUniversalNFT
by args.contract
. Ensure that the correct ABI (IERC721 or other advanced interface) is used for subsequent calls if the contract extends beyond basic ERC721.
37-37
: transferCrossChain
Usage
You pass args.destination
to the contract with an optional receiver
. Confirm that your contract can handle a scenario where receiver
is not the same as signer.address
.
53-53
: Success Log
Clear message for the user. Ensure that this log remains correct if the user changes the addresses or the flow.
66-66
: Renamed Parameter
--contract
replaced the old --from
. This is a better name but ensure existing script references are updated to avoid confusion.
96-96
: destination
Parameter
Renaming --to
to --destination
clarifies usage. Ensure the help text is equally clear that this expects a ZRC20 address for cross-chain bridging.
contracts/nft/scripts/localnet.sh (3)
59-59
: Parameter Updates: ZetaChain → Ethereum
You’ve replaced --to
with --destination
and --from
with --contract
. This aligns with your updated Hardhat task. Confirm other references in your documentation are also updated.
65-65
: Parameter Updates: Ethereum → BNB
Same parameter rename. Good consistency across chain calls.
71-71
: No --destination
Provided
Here you omit --destination
. If the contract’s default handling is to treat a missing destination
as a local chain transfer or a defined fallback, confirm that’s intended.
function transferCrossChain( | ||
address destination, | ||
address receiver, | ||
uint256 amount | ||
) external payable { | ||
if (receiver == address(0)) revert InvalidAddress(); | ||
_burn(msg.sender, amount); | ||
|
||
bytes memory message = abi.encode( | ||
destination, | ||
receiver, | ||
amount, | ||
msg.sender | ||
); | ||
if (destination == address(0)) { | ||
gateway.call( | ||
universal, | ||
message, | ||
RevertOptions(address(this), false, address(0), message, 0) | ||
); | ||
} else { | ||
gateway.depositAndCall{value: msg.value}( | ||
universal, | ||
message, | ||
RevertOptions( | ||
address(this), | ||
true, | ||
address(0), | ||
abi.encode(amount, msg.sender), | ||
gasLimitAmount | ||
) | ||
); | ||
} | ||
|
||
emit TokenTransfer(destination, receiver, amount); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Cross-chain transfer logic.
Burning tokens locally, building the message, and dispatching to gateway
is a typical cross-chain design.
Consider potential bridging re-entrancy or concurrency with repeated messages.
This refactor makes it possible to take an ERC-721 template from https://wizard.openzeppelin.com/#erc721, modify 5 lines and make it a universal NFT.
Turning any ERC-721 (assuming it's upgradeable) into a universal NFT is pretty cool.
We can create a non-upgradeable version in the future (sort of like OZ), but I don't think it's a priority. For one, how would you upgrade an existing ERC-721 if it's non-upgradeable?
Summary by CodeRabbit
New Features
Bug Fixes
Documentation
Chores