From bda3cbaffab1b893e60aa6b1a3298357b3f53b19 Mon Sep 17 00:00:00 2001 From: Logan Nguyen Date: Tue, 24 Oct 2023 20:16:05 -0500 Subject: [PATCH 1/4] feat: added TransactionInfo solidity example contract Signed-off-by: Logan Nguyen --- .../TransactionInfo.sol | 75 ++++++++ .../TransactionInfo.js | 164 ++++++++++++++++++ 2 files changed, 239 insertions(+) create mode 100644 contracts/solidity/yul/transaction-information/TransactionInfo.sol create mode 100644 test/solidity/yul/transaction-information/TransactionInfo.js diff --git a/contracts/solidity/yul/transaction-information/TransactionInfo.sol b/contracts/solidity/yul/transaction-information/TransactionInfo.sol new file mode 100644 index 000000000..fa7cc2d78 --- /dev/null +++ b/contracts/solidity/yul/transaction-information/TransactionInfo.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.20; + +contract TransactionInfo { + + constructor() payable {} + + /// gas still available to execution + function getGasLeft() external view returns (uint256 result) { + assembly{ + result := gas() + } + } + + /// address of the current contract / execution context + function getContractAddress() external view returns (address addr) { + assembly { + addr := address() + } + } + + /// get wei balance at address a + function getBalance(address a) external view returns (uint256 bal) { + assembly { + bal := balance(a) + } + } + + /// get self balance - equivalent to balance(address()), but cheaper + function getSelfBalance() external view returns (uint256 bal) { + assembly { + bal := selfbalance() + } + } + + /// get call sender + function getMsgCaller() external view returns (address msgCaller) { + assembly { + msgCaller := caller() + } + } + + /// get wei sent together with the current call + event CallValue(uint256 callBalance); + function getCallValue() external payable { + uint256 callBalance; + assembly { + callBalance := callvalue() + } + emit CallValue(callBalance); + } + + /// call msg.data starting from position p (32 bytes) + /// msg.data is a byte array that contains the function arguments encoded according to the function's signature. + function getCallDataLoad(uint256 p) external pure returns (bytes32 data) { + assembly { + data := calldataload(p) + } + } + + /// size of call data in bytes + function getCallDataSize() external pure returns (uint256 datasize) { + assembly { + datasize := calldatasize() + } + } + + /// calldatacopy(t, f, s) - copy `s` bytes from calldata at position `f` to memory at position `t` + function callDataCopier(uint256 t, uint256 f, uint256 s) external pure returns (bytes32 data) { + assembly { + calldatacopy(t, f, s) + data := mload(t) + } + } +} \ No newline at end of file diff --git a/test/solidity/yul/transaction-information/TransactionInfo.js b/test/solidity/yul/transaction-information/TransactionInfo.js new file mode 100644 index 000000000..a82be7769 --- /dev/null +++ b/test/solidity/yul/transaction-information/TransactionInfo.js @@ -0,0 +1,164 @@ +/*- + * + * 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') + +describe('@solidityequiv5 TransactionInfo Tests', () => { + let transactionInfoContract, signers + const GASLIMIT = 1000000 + const INITIAL_BALANCE = 30000000000 + const tinybarToWeibarCoef = 10_000_000_000 + + before(async () => { + signers = await ethers.getSigners() + const transactionInfoContractFactory = await ethers.getContractFactory( + 'TransactionInfo' + ) + transactionInfoContract = await transactionInfoContractFactory.deploy({ + value: INITIAL_BALANCE, + gasLimit: GASLIMIT, + }) + }) + + it('Should deploy with a call value', async () => { + const intialBalance = await ethers.provider.getBalance( + transactionInfoContract.address + ) + + expect(intialBalance).to.eq(INITIAL_BALANCE) + }) + + it('Should get the gas left', async () => { + const expectedGas = 9000132 + const result = await transactionInfoContract.getGasLeft() + + expect(result).to.eq(expectedGas) + }) + + it('Should get contract address', async () => { + const expectedContractAddress = transactionInfoContract.address + const result = await transactionInfoContract.getContractAddress() + + expect(result).to.eq(expectedContractAddress) + }) + + it('Should get contract balance', async () => { + const expectedSignerABalance = Math.round( + (await signers[0].getBalance()) / tinybarToWeibarCoef + ) + + const result = await transactionInfoContract.getBalance( + await signers[0].getAddress() + ) + + expect(result).to.eq(expectedSignerABalance) + }) + + it('Should get self balance', async () => { + const expectedSelfBalance = Math.round( + INITIAL_BALANCE / tinybarToWeibarCoef + ) + const result = await transactionInfoContract.getSelfBalance() + + expect(result).to.eq(expectedSelfBalance) + }) + + it('Should get message caller', async () => { + const expectedMessageCaller = signers[0].address + + const result = await transactionInfoContract.getMsgCaller() + + expect(result).to.eq(expectedMessageCaller) + }) + + it('Should get message call value', async () => { + const expectedValue = 10_000_000_000 + + const transaction = await transactionInfoContract.getCallValue({ + value: expectedValue, + }) + const receipt = await transaction.wait() + + const event = receipt.events.map((e) => e.event === 'CallValue' && e)[0] + + const [messageValue] = event.args + + expect(messageValue).to.eq(expectedValue / tinybarToWeibarCoef) + }) + + it('Should get message call data', async () => { + const index = 2 + const functionSig = 'getCallDataLoad(uint256)' + const callData = transactionInfoContract.interface + .encodeFunctionData(functionSig, [index]) + .replace('0x', '') + + // @notice since transactionInfoContract.getCallDataLoad() returns the msg.calldata from memory offset `index`, + // `bytes32CallData` also needs to dynamically truncate itself based on `index` + const expectedBytes32CallData = + `0x` + callData.slice(index * 2, 64 + index * 2) + + const result = await transactionInfoContract.getCallDataLoad(index) + + expect(result).to.eq(expectedBytes32CallData) + }) + + it('Should get the size of message call data', async () => { + const messagecallData = await transactionInfoContract.getCallDataLoad(0) + const callDataBytesArraay = ethers.utils.arrayify(messagecallData) + const significantBytesLength = callDataBytesArraay.reduce( + (length, byte) => { + if (byte !== 0) { + return (length += 1) + } else { + return length + } + }, + 0 + ) + + const result = await transactionInfoContract.getCallDataSize() + + expect(result).to.eq(significantBytesLength) + }) + + it('Should copy message call data to memory', async () => { + const dataPosF = 0 + const memPosT = 0x20 + const bytesAmountS = 4 // max amount + const functionSig = 'callDataCopier(uint256, uint256, uint256)' + + const messageCallData = transactionInfoContract.interface + .encodeFunctionData(functionSig, [memPosT, dataPosF, bytesAmountS]) + .replace('0x', '') + + const bytes32MessageCallData = + '0x' + messageCallData.slice(dataPosF * 2, 64 + dataPosF * 2) + + const result = await transactionInfoContract.callDataCopier( + memPosT, + dataPosF, + bytesAmountS + ) + + expect(result).to.eq(bytes32MessageCallData) + }) +}) From 3de277be4e57f271acd150fa799d9a604c750090 Mon Sep 17 00:00:00 2001 From: Logan Nguyen Date: Wed, 25 Oct 2023 11:40:51 -0500 Subject: [PATCH 2/4] update: added blank line Signed-off-by: Logan Nguyen --- .../solidity/yul/transaction-information/TransactionInfo.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/solidity/yul/transaction-information/TransactionInfo.sol b/contracts/solidity/yul/transaction-information/TransactionInfo.sol index fa7cc2d78..31ac5aa73 100644 --- a/contracts/solidity/yul/transaction-information/TransactionInfo.sol +++ b/contracts/solidity/yul/transaction-information/TransactionInfo.sol @@ -72,4 +72,5 @@ contract TransactionInfo { data := mload(t) } } -} \ No newline at end of file +} + From 702930644cae0f8b6d6505d9dc7899b852211608 Mon Sep 17 00:00:00 2001 From: Logan Nguyen Date: Wed, 25 Oct 2023 22:48:05 -0500 Subject: [PATCH 3/4] update: moved yul folder out of solidity Signed-off-by: Logan Nguyen --- .../yul/transaction-information/TransactionInfo.sol | 0 .../{solidity => }/yul/transaction-information/TransactionInfo.js | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename contracts/{solidity => }/yul/transaction-information/TransactionInfo.sol (100%) rename test/{solidity => }/yul/transaction-information/TransactionInfo.js (100%) diff --git a/contracts/solidity/yul/transaction-information/TransactionInfo.sol b/contracts/yul/transaction-information/TransactionInfo.sol similarity index 100% rename from contracts/solidity/yul/transaction-information/TransactionInfo.sol rename to contracts/yul/transaction-information/TransactionInfo.sol diff --git a/test/solidity/yul/transaction-information/TransactionInfo.js b/test/yul/transaction-information/TransactionInfo.js similarity index 100% rename from test/solidity/yul/transaction-information/TransactionInfo.js rename to test/yul/transaction-information/TransactionInfo.js From f412f240d297b02a61819149fca6cc27e7390d97 Mon Sep 17 00:00:00 2001 From: Logan Nguyen Date: Thu, 26 Oct 2023 15:45:57 -0500 Subject: [PATCH 4/4] update: added more opcodes Signed-off-by: Logan Nguyen --- .../TransactionInfo.sol | 49 +++++++++++++++++ .../TransactionInfo.js | 54 +++++++++++++++++-- 2 files changed, 100 insertions(+), 3 deletions(-) diff --git a/contracts/yul/transaction-information/TransactionInfo.sol b/contracts/yul/transaction-information/TransactionInfo.sol index 31ac5aa73..b5d3a50f5 100644 --- a/contracts/yul/transaction-information/TransactionInfo.sol +++ b/contracts/yul/transaction-information/TransactionInfo.sol @@ -72,5 +72,54 @@ contract TransactionInfo { data := mload(t) } } + + /// chainid() - ID of the executing chain + function getChainId() external view returns (uint256 chainId) { + assembly { + chainId := chainid() + } + } + + /// origin() - transaction sender + function getOrigin() external view returns (address originSender) { + assembly{ + originSender := origin() + } + } + + /// gasprice() - gas price of the transaction + function getGasPrice() external view returns (uint256 gasPrice) { + assembly { + gasPrice := gasprice() + } + } + + /// coinbase() - current mining beneficiary + function getCoinbase() external view returns (address beneficiary) { + assembly { + beneficiary := coinbase() + } + } + + /// timestamp() - timestamp of the current block in seconds since the epoch + function getTimestamp() external view returns (uint256 currentTimestamp) { + assembly { + currentTimestamp := timestamp() + } + } + + /// number() - current block number + function getCurrentBlockNumber() external view returns (uint256 blockNumber) { + assembly { + blockNumber := number() + } + } + + /// gaslimit() - block gas limit of the current block + function getGasLimit() external view returns (uint256 gasLimit) { + assembly { + gasLimit := gaslimit() + } + } } diff --git a/test/yul/transaction-information/TransactionInfo.js b/test/yul/transaction-information/TransactionInfo.js index a82be7769..0fed16466 100644 --- a/test/yul/transaction-information/TransactionInfo.js +++ b/test/yul/transaction-information/TransactionInfo.js @@ -19,7 +19,7 @@ */ const { expect } = require('chai') -const { ethers } = require('hardhat') +const { ethers, network } = require('hardhat') describe('@solidityequiv5 TransactionInfo Tests', () => { let transactionInfoContract, signers @@ -47,10 +47,9 @@ describe('@solidityequiv5 TransactionInfo Tests', () => { }) it('Should get the gas left', async () => { - const expectedGas = 9000132 const result = await transactionInfoContract.getGasLeft() - expect(result).to.eq(expectedGas) + expect(result).to.gt(0) }) it('Should get contract address', async () => { @@ -161,4 +160,53 @@ describe('@solidityequiv5 TransactionInfo Tests', () => { expect(result).to.eq(bytes32MessageCallData) }) + + it('Should get current chainID', async () => { + const chainId = await transactionInfoContract.getChainId() + const expectedChainId = ethers.provider.network.chainId + + expect(chainId).to.eq(expectedChainId) + }) + + it('Should get original sender', async () => { + const originalSender = await transactionInfoContract.getOrigin() + const expectedSender = await signers[0].getAddress() + + expect(originalSender).to.eq(expectedSender) + }) + + it('Should get gas price', async () => { + const gasPrice = await transactionInfoContract.getGasPrice() + + expect(gasPrice).to.eq(1) + }) + + it('Should get coinbase', async () => { + const coinbase = await transactionInfoContract.getCoinbase() + + expect(ethers.utils.isAddress(coinbase)).to.be.true + }) + + it('Should get current block timestamp', async () => { + const blockTimestamp = await transactionInfoContract.getTimestamp() + + const expectedTimeStamp = Math.floor(Date.now() / 1000) + + expect(blockTimestamp).to.lte(expectedTimeStamp) + }) + + it('Should get current block number', async () => { + const currentBlockNumber = + await transactionInfoContract.getCurrentBlockNumber() + + expect(currentBlockNumber).to.gt(0) + }) + + it('Should get gas limit', async () => { + const gasLimit = await transactionInfoContract.getGasLimit({ + gasLimit: GASLIMIT, + }) + + expect(gasLimit).to.eq(GASLIMIT) + }) })