Skip to content
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

feat: added TransactionInfo solidity example contract (#527) #528

Merged
merged 4 commits into from
Nov 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 125 additions & 0 deletions contracts/yul/transaction-information/TransactionInfo.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// 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)
}
}

/// 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()
}
}
}

212 changes: 212 additions & 0 deletions test/yul/transaction-information/TransactionInfo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
/*-
*
* 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, network } = 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 result = await transactionInfoContract.getGasLeft()

expect(result).to.gt(0)
})

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)
})

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)
})
})
Loading