diff --git a/contracts/solidity/encoding/Encoding.sol b/contracts/solidity/encoding/Encoding.sol new file mode 100644 index 000000000..805757102 --- /dev/null +++ b/contracts/solidity/encoding/Encoding.sol @@ -0,0 +1,43 @@ +// Encoding.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +contract Encoding { + event Added(uint256 result); + + function add(uint256 a, uint256 b) public pure returns (uint256) { + return a + b; + } + + function decodeData(bytes memory encodedData) public pure returns (address, uint256) { + address decodedAddress; + uint256 decodedUint; + assembly { + decodedAddress := mload(add(encodedData, 32)) + decodedUint := mload(add(encodedData, 64)) + } + return (decodedAddress, decodedUint); + } + + function encodeData(address _address, uint256 _uint) public pure returns (bytes memory) { + return abi.encode(_address, _uint); + } + + function encodeAddFunction(uint256 a, uint256 b) public pure returns (bytes memory) { + bytes4 selector = this.add.selector; + + return abi.encodeWithSelector(selector, a, b); + } + + function getPackedData(address _addr, uint256 _amount, string memory _data) public pure returns (bytes memory) { + return abi.encodePacked(_addr, _amount, _data); + } + + function executeAddFunction(uint256 a, uint256 b) public { + bytes memory data = encodeAddFunction(a, b); + (bool success, bytes memory result) = address(this).call(data); + + require(success, "Call failed"); + emit Added(abi.decode(result, (uint256))); + } +} diff --git a/contracts/solidity/encoding/Receiver.sol b/contracts/solidity/encoding/Receiver.sol new file mode 100644 index 000000000..56627fcf4 --- /dev/null +++ b/contracts/solidity/encoding/Receiver.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +contract Receiver { + event ReceivedData(uint256 data); + + function receiveData(uint256 data) external { + emit ReceivedData(data); + } +} diff --git a/contracts/solidity/encoding/Sender.sol b/contracts/solidity/encoding/Sender.sol new file mode 100644 index 000000000..3a57438d5 --- /dev/null +++ b/contracts/solidity/encoding/Sender.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "./Receiver.sol"; + +contract Sender { + Receiver public receiver; + + constructor(address _receiver) { + receiver = Receiver(_receiver); + } + + function sendDataEncodeWithSignature(uint256 data) public { + bytes memory payload = abi.encodeWithSignature("receiveData(uint256)", data); + + (bool success,) = address(receiver).call(payload); + require(success, "External call using abi.encodeWithSignature failed"); + } + + function sendDataEncodeCall(uint256 data) public { + bytes memory payload = abi.encodeCall( + Receiver(address(receiver)).receiveData, + (data) + ); + + (bool success,) = address(receiver).call(payload); + require(success, "External call using abi.encodeCall failed"); + } + +} diff --git a/test/multicall/Multicall.js b/test/multicall/Multicall.js index 51f371c6c..bc920cb46 100644 --- a/test/multicall/Multicall.js +++ b/test/multicall/Multicall.js @@ -2,7 +2,7 @@ * * Hedera JSON RPC Relay - Hardhat Example * - * Copyright (C) 2022 Hedera Hashgraph, LLC + * Copyright (C) 2023 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -157,7 +157,7 @@ describe('Multicall Test Suite', function () { before(async () => { multicaller = await deployContract('Multicall3') - receiver = await deployContract('Receiver') + receiver = await deployContract('contracts/multicaller/Receiver.sol:Receiver') reverter = await deployContract('Reverter') }) diff --git a/test/solidity/encoding/Encoding.js b/test/solidity/encoding/Encoding.js new file mode 100644 index 000000000..329ef4c91 --- /dev/null +++ b/test/solidity/encoding/Encoding.js @@ -0,0 +1,136 @@ +/*- + * + * Hedera JSON RPC Relay - Hardhat Example + * + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +const { expect } = require("chai"); +const { ethers } = require("hardhat"); + +describe("@solidityevmequiv Encoding", function() { + let encodingContract, receiver, sender; + + const addressData = "0x1234567890123456789012345678901234567890"; + const uintData = 123456789; + + beforeEach(async function() { + const Encoding = await ethers.getContractFactory("Encoding"); + encodingContract = await Encoding.deploy(); + await encodingContract.deployed(); + + const Receiver = await ethers.getContractFactory("contracts/solidity/encoding/Receiver.sol:Receiver"); + receiver = await Receiver.deploy(); + await receiver.deployed(); + + const Sender = await ethers.getContractFactory("Sender"); + sender = await Sender.deploy(receiver.address); + await sender.deployed(); + + }); + + it("Should decode data", async function() { + const encodedData = ethers.utils.defaultAbiCoder.encode( + ["address", "uint256"], + [addressData, uintData] + ); + + const result = await encodingContract.decodeData(encodedData); + + expect(result[0]).to.equal(addressData); + expect(result[1]).to.equal(uintData); + }); + + it("Should encode data", async function() { + const result = await encodingContract.encodeData(addressData, uintData); + + const decodedData = ethers.utils.defaultAbiCoder.decode( + ["address", "uint256"], + result + ); + + expect(decodedData[0]).to.equal(addressData); + expect(decodedData[1]).to.equal(uintData); + }); + + it("Should encode pack data", async function() { + const address = "0x1234567890123456789012345678901234567890"; + const amount = 100; + const data = "Hello, World!"; + + const packedData = encodePacked(address, amount, data); + const result = await encodingContract.getPackedData(address, amount, data); + expect(result).to.equal(packedData); + }); + + it("Should execute the add function and return the correct result to illustrate abi.encodeWitSelector", async function() { + const a = 5; + const b = 7; + + // Verify that the add function returns the correct result + const sum = await encodingContract.add(a, b); + expect(sum).to.equal(a + b); + + // Call the encodeAddFunction + const encodedData = await encodingContract.encodeAddFunction(a, b); + + // Extract the selector and encoded arguments + const selector = encodedData.slice(0, 10); + const encodedArgs = "0x" + encodedData.slice(10); + + // Verify the selector matches the add function's selector + expect(selector).to.equal(encodingContract.interface.getSighash("add")); + + const [decodedA, decodedB] = ethers.utils.defaultAbiCoder.decode(["uint256", "uint256"], encodedArgs); + expect(decodedA).to.equal(a); + expect(decodedB).to.equal(b); + + const tx = await encodingContract.executeAddFunction(a, b); + const receipt = await tx.wait(); + + expect(receipt.events.length).to.equal(1); + expect(receipt.events[0].event).to.equal("Added"); + + const eventResult = receipt.events[0].args[0].toNumber(); + expect(eventResult).to.equal(a + b); + }); + + it("Should call receiveData in Receiver contract via Sender using abi.encodeWithSignature", async function() { + const dataToSend = 12345; + + await expect(sender.sendDataEncodeWithSignature(dataToSend)) + .to.emit(receiver, "ReceivedData") + .withArgs(dataToSend); + }); + + it("Should call receiveData in Receiver contract via Sender using abi.encodeCall", async function() { + const dataToSend = 12345; + + await expect(sender.sendDataEncodeCall(dataToSend)) + .to.emit(receiver, "ReceivedData") + .withArgs(dataToSend); + }); + + +}); + +function encodePacked(address, amount, data) { + const addressBytes = ethers.utils.arrayify(address); + const amountBytes = ethers.utils.zeroPad(ethers.utils.arrayify(amount), 32); + const dataBytes = ethers.utils.toUtf8Bytes(data); + + return ethers.utils.hexConcat([addressBytes, amountBytes, dataBytes]); +} +