diff --git a/examples/call/contracts/Connected.sol b/examples/call/contracts/Connected.sol index 904018d..6918349 100644 --- a/examples/call/contracts/Connected.sol +++ b/examples/call/contracts/Connected.sol @@ -19,16 +19,6 @@ contract Connected { gateway = GatewayEVM(gatewayAddress); } - function hello(string memory message) external payable { - emit HelloEvent("Hello on EVM", message); - } - - function onRevert( - RevertContext calldata revertContext - ) external onlyGateway { - emit RevertEvent("Revert on EVM", revertContext); - } - function deposit( address receiver, RevertOptions memory revertOptions @@ -56,6 +46,10 @@ contract Connected { ); } + function hello(string memory message) external payable { + emit HelloEvent("Hello on EVM", message); + } + function onCall( MessageContext calldata context, bytes calldata message @@ -65,6 +59,12 @@ contract Connected { return ""; } + function onRevert( + RevertContext calldata revertContext + ) external onlyGateway { + emit RevertEvent("Revert on EVM", revertContext); + } + receive() external payable {} fallback() external payable {} diff --git a/examples/call/contracts/Universal.sol b/examples/call/contracts/Universal.sol index bd37fda..a71e724 100644 --- a/examples/call/contracts/Universal.sol +++ b/examples/call/contracts/Universal.sol @@ -22,37 +22,6 @@ contract Universal is UniversalContract { gateway = GatewayZEVM(gatewayAddress); } - function onCall( - MessageContext calldata context, - address zrc20, - uint256 amount, - bytes calldata message - ) external override onlyGateway { - string memory name = abi.decode(message, (string)); - emit HelloEvent("Hello on ZetaChain", name); - } - - function onRevert( - RevertContext calldata revertContext - ) external onlyGateway { - emit RevertEvent("Revert on ZetaChain", revertContext); - } - - function call( - bytes memory receiver, - address zrc20, - bytes calldata message, - uint256 gasLimit, - RevertOptions memory revertOptions - ) external { - (, uint256 gasFee) = IZRC20(zrc20).withdrawGasFeeWithGasLimit(gasLimit); - if (!IZRC20(zrc20).transferFrom(msg.sender, address(this), gasFee)) - revert TransferFailed(); - IZRC20(zrc20).approve(address(gateway), gasFee); - CallOptions memory callOptions = CallOptions(gasLimit, true); - gateway.call(receiver, zrc20, message, callOptions, revertOptions); - } - function withdraw( bytes memory receiver, uint256 amount, @@ -77,16 +46,32 @@ contract Universal is UniversalContract { gateway.withdraw(receiver, amount, zrc20, revertOptions); } + function call( + bytes memory receiver, + address zrc20, + bytes calldata message, + CallOptions memory callOptions, + RevertOptions memory revertOptions + ) external { + (, uint256 gasFee) = IZRC20(zrc20).withdrawGasFeeWithGasLimit( + callOptions.gasLimit + ); + if (!IZRC20(zrc20).transferFrom(msg.sender, address(this), gasFee)) + revert TransferFailed(); + IZRC20(zrc20).approve(address(gateway), gasFee); + gateway.call(receiver, zrc20, message, callOptions, revertOptions); + } + function withdrawAndCall( bytes memory receiver, uint256 amount, address zrc20, bytes calldata message, - uint256 gasLimit, + CallOptions memory callOptions, RevertOptions memory revertOptions ) external { (address gasZRC20, uint256 gasFee) = IZRC20(zrc20) - .withdrawGasFeeWithGasLimit(gasLimit); + .withdrawGasFeeWithGasLimit(callOptions.gasLimit); uint256 target = zrc20 == gasZRC20 ? amount + gasFee : amount; if (!IZRC20(zrc20).transferFrom(msg.sender, address(this), target)) revert TransferFailed(); @@ -101,7 +86,6 @@ contract Universal is UniversalContract { ) revert TransferFailed(); IZRC20(gasZRC20).approve(address(gateway), gasFee); } - CallOptions memory callOptions = CallOptions(gasLimit, false); gateway.withdrawAndCall( receiver, amount, @@ -111,4 +95,20 @@ contract Universal is UniversalContract { revertOptions ); } + + function onCall( + MessageContext calldata context, + address zrc20, + uint256 amount, + bytes calldata message + ) external override onlyGateway { + string memory name = abi.decode(message, (string)); + emit HelloEvent("Hello on ZetaChain", name); + } + + function onRevert( + RevertContext calldata revertContext + ) external onlyGateway { + emit RevertEvent("Revert on ZetaChain", revertContext); + } } diff --git a/examples/call/scripts/test.sh b/examples/call/scripts/test.sh index 6d5728c..8fa6d32 100755 --- a/examples/call/scripts/test.sh +++ b/examples/call/scripts/test.sh @@ -70,6 +70,17 @@ npx hardhat universal-withdraw-and-call \ --function "hello(string)" \ --amount 1 \ --network localhost \ + --call-options-is-arbitrary-call \ + --types '["string"]' hello + +npx hardhat localnet-check + +npx hardhat universal-withdraw-and-call \ + --contract "$CONTRACT_ZETACHAIN" \ + --receiver "$CONTRACT_ETHEREUM" \ + --zrc20 "$ZRC20_ETHEREUM" \ + --amount 1 \ + --network localhost \ --types '["string"]' hello npx hardhat localnet-check diff --git a/examples/call/tasks/universalCall.ts b/examples/call/tasks/universalCall.ts index 4e7b04f..315c791 100644 --- a/examples/call/tasks/universalCall.ts +++ b/examples/call/tasks/universalCall.ts @@ -11,6 +11,11 @@ const main = async (args: any, hre: HardhatRuntimeEnvironment) => { gasLimit: args.txOptionsGasLimit, }; + const callOptions = { + isArbitraryCall: args.callOptionsIsArbitraryCall, + gasLimit: args.callOptionsGasLimit, + }; + const revertOptions = { abortAddress: "0x0000000000000000000000000000000000000000", // not used callOnRevert: args.callOnRevert, @@ -69,7 +74,7 @@ const main = async (args: any, hre: HardhatRuntimeEnvironment) => { ethers.utils.hexlify(args.receiver), args.zrc20, message, - gasLimit, + callOptions, revertOptions, txOptions ); @@ -114,6 +119,13 @@ task( 7000000, types.int ) + .addFlag("callOptionsIsArbitraryCall", "Call any function") + .addOptionalParam( + "callOptionsGasLimit", + "The gas limit for the call", + 7000000, + types.int + ) .addParam("function", `Function to call (example: "hello(string)")`) .addParam("name", "The name of the contract", "Universal") .addParam("types", `The types of the parameters (example: '["string"]')`) diff --git a/examples/call/tasks/universalWithdrawAndCall.ts b/examples/call/tasks/universalWithdrawAndCall.ts index f1cafd3..d23d4ed 100644 --- a/examples/call/tasks/universalWithdrawAndCall.ts +++ b/examples/call/tasks/universalWithdrawAndCall.ts @@ -6,11 +6,24 @@ const main = async (args: any, hre: HardhatRuntimeEnvironment) => { const { ethers } = hre; const [signer] = await ethers.getSigners(); + if (args.callOptionsIsArbitraryCall && !args.function) { + throw new Error("Function is required for arbitrary calls"); + } + + if (!args.callOptionsIsArbitraryCall && args.function) { + throw new Error("Function is not allowed for non-arbitrary calls"); + } + const txOptions = { gasPrice: args.txOptionsGasPrice, gasLimit: args.txOptionsGasLimit, }; + const callOptions = { + isArbitraryCall: args.callOptionsIsArbitraryCall, + gasLimit: args.callOptionsGasLimit, + }; + const revertOptions = { abortAddress: "0x0000000000000000000000000000000000000000", // not used callOnRevert: args.callOnRevert, @@ -21,8 +34,6 @@ const main = async (args: any, hre: HardhatRuntimeEnvironment) => { ), }; - const functionSignature = ethers.utils.id(args.function).slice(0, 10); - const types = JSON.parse(args.types); if (types.length !== args.values.length) { @@ -51,9 +62,16 @@ const main = async (args: any, hre: HardhatRuntimeEnvironment) => { valuesArray ); - const message = ethers.utils.hexlify( - ethers.utils.concat([functionSignature, encodedParameters]) - ); + let message; + + if (args.isArbitraryCall) { + let functionSignature = ethers.utils.id(args.function).slice(0, 10); + message = ethers.utils.hexlify( + ethers.utils.concat([functionSignature, encodedParameters]) + ); + } else { + message = encodedParameters; + } const gasLimit = hre.ethers.BigNumber.from(args.txOptionsGasLimit); @@ -86,7 +104,7 @@ const main = async (args: any, hre: HardhatRuntimeEnvironment) => { amount, args.zrc20, message, - gasLimit, + callOptions, revertOptions, txOptions ); @@ -132,7 +150,14 @@ task( 7000000, types.int ) - .addParam("function", `Function to call (example: "hello(string)")`) + .addFlag("callOptionsIsArbitraryCall", "Call any function") + .addOptionalParam( + "callOptionsGasLimit", + "The gas limit for the call", + 7000000, + types.int + ) + .addOptionalParam("function", `Function to call (example: "hello(string)")`) .addParam("name", "The name of the contract", "Universal") .addParam("amount", "Amount of ZRC-20 to withdraw") .addParam("types", `The types of the parameters (example: '["string"]')`)