diff --git a/.gitignore b/.gitignore index f76a3b192..2025916ae 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,6 @@ cache artifacts/@openzeppelin artifacts/build-info artifacts/contracts - .openzeppelin/unknown-298.json .env test-results.* diff --git a/contracts/solidity/transaction/MessageFrameAddresses.sol b/contracts/solidity/transaction/MessageFrameAddresses.sol new file mode 100644 index 000000000..606592a78 --- /dev/null +++ b/contracts/solidity/transaction/MessageFrameAddresses.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.20; + +contract MessageFrameAddresses { + function getTxOrigin() external view returns (address) { + return tx.origin; + } + + function getMsgSender() external view returns (address) { + return msg.sender; + } +} diff --git a/contracts/solidity/transaction/Transaction.sol b/contracts/solidity/transaction/Transaction.sol new file mode 100644 index 000000000..0cf3d26c6 --- /dev/null +++ b/contracts/solidity/transaction/Transaction.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.20; + +import { MessageFrameAddresses } from "./MessageFrameAddresses.sol"; + +contract Transaction { + string public message; + uint myInteger; + address messageFrameAddresses; + MessageFrameAddresses mfContract; + event MsgValue(uint256); + + constructor(address addr) { + messageFrameAddresses = addr; + mfContract = MessageFrameAddresses(payable(addr)); + } + + function checkGasleft() external view returns (uint256) { + return gasleft(); + } + + function getMessageData(uint integer, string memory inputMessage) external returns (bytes memory) { + message = inputMessage; + myInteger = integer; + + return msg.data; + } + + function getMessageSender() external view returns (address) { + return msg.sender; + } + + function getMessageSignature() external pure returns (bytes4) { + return msg.sig; + } + + function getMessageValue() external payable { + emit MsgValue(msg.value); + } + + function getGasPrice() external view returns (uint256) { + return tx.gasprice; + } + + function getTxOriginFromSecondary() external view returns (address) { + return mfContract.getTxOrigin(); + } + + function getMsgSenderFromSecondary() external view returns (address) { + return mfContract.getMsgSender(); + } +} diff --git a/test/constants.js b/test/constants.js index e38089fbd..392a7cd7e 100644 --- a/test/constants.js +++ b/test/constants.js @@ -101,6 +101,8 @@ const Contract = { PrngSystemContract: 'PrngSystemContract', Concatenation: 'Concatenation', Errors: 'Errors', + Transaction: 'Transaction', + MessageFrameAddresses: 'MessageFrameAddresses', } const CALL_EXCEPTION = 'CALL_EXCEPTION' diff --git a/test/solidity/transaction/transaction.js b/test/solidity/transaction/transaction.js new file mode 100644 index 000000000..d3d97f2dd --- /dev/null +++ b/test/solidity/transaction/transaction.js @@ -0,0 +1,119 @@ +/*- + * + * Hedera Smart Contracts + * + * 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') +const Constants = require('../../constants') +const Utils = require('../../hts-precompile/utils') + +describe('Transaction tests', function () { + let contractTr, contractTrAddr, wallet, mfContract, senderWalletAddr + + before(async function () { + const factoryTrasnactionContract = await ethers.getContractFactory( + Constants.Contract.Transaction + ) + const factoryMfContract = await ethers.getContractFactory( + Constants.Contract.MessageFrameAddresses + ) + + mfContract = await factoryMfContract.deploy() + await mfContract.deployed(); + contractTr = await factoryTrasnactionContract.deploy(mfContract.address) + await contractTr.deployed(); + contractTrAddr = contractTr.address + + const signers = await ethers.getSigners() + wallet = signers[0]; + senderWalletAddr = await wallet.getAddress() + }) + + it('gasleft() returns (uint256): remaining gas', async function () { + const STARTING_GAS = 30000; + const gasLeft = await contractTr.checkGasleft({ gasLimit: STARTING_GAS }) + + expect(ethers.BigNumber.isBigNumber(gasLeft)).to.be.true + expect(gasLeft.gt(ethers.BigNumber.from(0))).to.be.true; + expect(gasLeft.lt(ethers.BigNumber.from(STARTING_GAS))).to.be.true; + }) + + it('msg.data (bytes calldata): complete calldata', async function () { + const myString = 'Hello, world!'; + const txRes = await contractTr.getMessageData(12, myString); + const returnedData = txRes.data; + + const ABI = [ + "function getMessageData(uint integer, string memory inputMessage)" + ]; + const interface = new ethers.utils.Interface(ABI); + const encodedFunction = interface.encodeFunctionData("getMessageData", [ 12, myString ]); + + expect(returnedData).to.exist; + expect(returnedData).to.be.equal(encodedFunction); + }) + + it('msg.sender (address): sender of the message (current call)', async function () { + const sender = await contractTr.getMessageSender(); + + expect(sender).to.exist; + expect(sender).to.be.equal(senderWalletAddr); + }) + + it('msg.sig (bytes4): first four bytes of the calldata (i.e. function identifier)', async function () { + const msgSig = await contractTr.getMessageSignature(); + + const ABI = [ + "function getMessageSignature()" + ]; + const interface = new ethers.utils.Interface(ABI); + const encodedFunctionSig = interface.encodeFunctionData("getMessageSignature"); + + expect(msgSig).to.exist; + expect(msgSig).to.be.equal(encodedFunctionSig); + }) + + it('msg.value (uint): number of wei sent with the message', async function () { + const valueToSend = ethers.utils.parseEther(String(1)); + const txRes = await contractTr.getMessageValue({value: valueToSend}); + const receipt = await txRes.wait(); + const amount = receipt.events[0].args[0]; + ethers.utils.formatEther(amount); + + // to compare with the value sent, we need to convert to tinybar + expect(amount.mul(Utils.tinybarToWeibarCoef)).to.equal(valueToSend); + }) + + it('tx.gasprice (uint): gas price of the transaction', async function () { + const gasPrice = await contractTr.getGasPrice(); + + expect(ethers.BigNumber.isBigNumber(gasPrice)).to.be.true; + expect(gasPrice.gt(ethers.BigNumber.from(0))).to.be.true; + }) + + it('tx.origin (address): sender of the transaction (full call chain)', async function () { + const originAddr = await contractTr.getTxOriginFromSecondary(); + const msgSender = await contractTr.getMsgSenderFromSecondary(); + + expect(originAddr).to.exist; + expect(msgSender).to.exist; + expect(originAddr).to.be.equal(senderWalletAddr); + expect(msgSender).to.be.equal(contractTr.address); + }) +})