Skip to content

Commit

Permalink
feat: added TransactionInfo solidity example contract
Browse files Browse the repository at this point in the history
Signed-off-by: Logan Nguyen <[email protected]>
  • Loading branch information
quiet-node committed Oct 25, 2023
1 parent 1b45c1e commit bda3cba
Show file tree
Hide file tree
Showing 2 changed files with 239 additions and 0 deletions.
75 changes: 75 additions & 0 deletions contracts/solidity/yul/transaction-information/TransactionInfo.sol
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
164 changes: 164 additions & 0 deletions test/solidity/yul/transaction-information/TransactionInfo.js
Original file line number Diff line number Diff line change
@@ -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)
})
})

0 comments on commit bda3cba

Please sign in to comment.