Skip to content

Commit

Permalink
feat: added ContractCaller solidity example contract (#534) (#535)
Browse files Browse the repository at this point in the history
feat: added ContractCaller solidity example contract

Signed-off-by: Logan Nguyen <[email protected]>
  • Loading branch information
quiet-node authored Nov 7, 2023
1 parent 7864827 commit ce381d4
Show file tree
Hide file tree
Showing 3 changed files with 265 additions and 1 deletion.
106 changes: 106 additions & 0 deletions contracts/yul/contract-caller/ContractCaller.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.20;

contract TargetContract {
uint256 count;

constructor(uint256 _count) {
count = _count;
}

function setCount(uint256 _count) external {
count = _count;
}

function getCount() external view returns (uint256) {
return count;
}
}


contract ContractCaller {
uint256 public count;

event CallResult(bool success);
event CallReturnedData(uint256 count);

/// call(g, a, v, in, insize, out, outsize)
/// `g`: Amount of gas to be provided for the execution of called contract
/// `a`: Address of the called contract
/// `v`: callvalue() (a.k.a. msg.value)
/// `in`: Input data that will be provided to the called contract
/// `insize`: Input data size
/// `out`: Output data produced by the called contract
/// `outsize`: Output data size
function call(uint256 gasLimit, address payable _targetContractAddress, bytes memory input) external payable {
bool success;
uint256 returnedData;

assembly {
let returnedDataPlaceholder := mload(0x40) // load the data at free memory pointer

success := call(gasLimit, _targetContractAddress, callvalue(), add(input, 0x20), mload(input), returnedDataPlaceholder, 0x20)

returnedData := mload(returnedDataPlaceholder)
}

emit CallResult(success);
emit CallReturnedData(returnedData);
}

/// staticcall(g, a, in, insize, out, outsize) - identical to `call` but do not allow state modifications
/// `g`: Amount of gas to be provided for the execution of called contract
/// `a`: Address of the called contract
/// `in`: Input data that will be provided to the called contract
/// `insize`: Input data size
/// `out`: Output data produced by the called contract
/// `outsize`: Output data size
function staticcall(uint256 gasLimit, address payable _targetContractAddress, bytes memory input) external {
bool success;
uint256 returnedData;

assembly {
let returnedDataPlaceholder := mload(0x40) // load the data at free memory pointer

success := staticcall(gasLimit, _targetContractAddress, add(input, 0x20), mload(input), returnedDataPlaceholder, 0x20)

returnedData := mload(returnedDataPlaceholder)
}

emit CallResult(success);
emit CallReturnedData(returnedData);
}


/// callcode(g, a, v, in, insize, out, outsize) - identical to `call` but only use the code from a and stay in the context of the current contract otherwise
/// `g`: Amount of gas to be provided for the execution of called contract
/// `a`: Address of the called contract
/// `in`: Input data that will be provided to the called contract
/// `insize`: Input data size
/// `out`: Output data produced by the called contract
/// `outsize`: Output data size
function callCode(uint256 gasLimit, address payable _targetContractAddress, bytes memory input) external payable {
bool success;
assembly {
/// @notice callcode uses the code from `_targetContractAddress` to update current contract's states
success := callcode(gasLimit, _targetContractAddress, callvalue(), add(input, 0x20), mload(input), 0, 0)
}
emit CallResult(success);
}

/// delegatecall(g, a, in, insize, out, outsize) - identical to `callcode` but also keep caller and callvalue
/// `g`: Amount of gas to be provided for the execution of called contract
/// `a`: Address of the called contract
/// `in`: Input data that will be provided to the called contract
/// `insize`: Input data size
/// `out`: Output data produced by the called contract
/// `outsize`: Output data size
function delegateCall(uint256 gasLimit, address payable _targetContractAddress, bytes memory input) external {
bool success;
assembly {
/// @notice delegatecall uses the code from `_targetContractAddress` to update current contract's states
success := delegatecall(gasLimit, _targetContractAddress, add(input, 0x20), mload(input), 0, 0)
}
emit CallResult(success);
}
}
2 changes: 1 addition & 1 deletion contracts/yul/math-coverage/MatchCoverage.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-License-Identifier: UNLICENSED
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.20;

contract MathCoverage {
Expand Down
158 changes: 158 additions & 0 deletions test/yul/contract-caller/ContractCaller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*-
*
* 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 Contract Caller Tests', async () => {
let contractCaller, targetContract, getCountEncodedSig, setCountEncodedSig
const COUNT_A = 3
const GAS = 1_000_000
const INITIAL_COUNT = 9

beforeEach(async () => {
// deploy contracts
const contractCallerFactory = await ethers.getContractFactory(
'ContractCaller'
)
const targetContractFactory = await ethers.getContractFactory(
'TargetContract'
)
contractCaller = await contractCallerFactory.deploy()
targetContract = await targetContractFactory.deploy(INITIAL_COUNT)

// prepare encoded function signatures
getCountEncodedSig =
targetContract.interface.encodeFunctionData('getCount()')

setCountEncodedSig = targetContract.interface.encodeFunctionData(
'setCount(uint256)',
[COUNT_A]
)
})

it('Should execute call(g, a, v, in, insize, out, outsize)', async () => {
// prepare transactions
const callSetCountTx = await contractCaller.call(
GAS,
targetContract.address,
setCountEncodedSig
)
const callGetCountTx = await contractCaller.call(
GAS,
targetContract.address,
getCountEncodedSig
)

// wait for the receipts
const callSetCountReceipt = await callSetCountTx.wait()
const callGetCountReceipt = await callGetCountTx.wait()

// extract events
const [callSetCountResult] = callSetCountReceipt.events.map(
(e) => e.event === 'CallResult' && e
)[0].args
const [callGetCountResult] = callGetCountReceipt.events.map(
(e) => e.event === 'CallResult' && e
)[0].args
const [callGetCountReturnedData] = callGetCountReceipt.events.map(
(e) => e.event === 'CallReturnedData' && e
)[1].args

// assertion
expect(callSetCountResult).to.be.true
expect(callGetCountResult).to.be.true
expect(callGetCountReturnedData).to.eq(COUNT_A)
})

it('Should execute staticcall(g, a, in, insize, out, outsize)', async () => {
// prepare transactions
const callGetCountTx = await contractCaller.staticcall(
GAS,
targetContract.address,
getCountEncodedSig
)

// wait for the receipts
const callGetCountReceipt = await callGetCountTx.wait()

// extract events
const [callGetCountResult] = callGetCountReceipt.events.map(
(e) => e.event === 'CallResult' && e
)[0].args
const [callGetCountReturnedData] = callGetCountReceipt.events.map(
(e) => e.event === 'CallReturnedData' && e
)[1].args

// assertion
expect(callGetCountResult).to.be.true
expect(callGetCountReturnedData).to.eq(INITIAL_COUNT)
})

it('Should execute callcode(g, a, v, in, insize, out, outsize)', async () => {
// prepare transactions
const callSetCountTx = await contractCaller.callCode(
GAS,
targetContract.address,
setCountEncodedSig
)

// wait for the receipts
const callSetCountReceipt = await callSetCountTx.wait()

// extract events
const [callSetCountResult] = callSetCountReceipt.events.map(
(e) => e.event === 'CallResult' && e
)[0].args

// get storage count within ContractCaller contract
const storageCount = await contractCaller.count()

// @notice since callcode use the code from `targetContract` to update `ContractCaller` contract
// => `storageCount` is expected to equal `COUNT_A`
expect(storageCount).to.eq(COUNT_A)
expect(callSetCountResult).to.be.true
})

it('Should execute delegatecall(g, a, in, insize, out, outsize)', async () => {
// prepare transactions
const callSetCountTx = await contractCaller.delegateCall(
GAS,
targetContract.address,
setCountEncodedSig
)

// wait for the receipts
const callSetCountReceipt = await callSetCountTx.wait()

// extract events
const [callSetCountResult] = callSetCountReceipt.events.map(
(e) => e.event === 'CallResult' && e
)[0].args

// get storage count within ContractCaller contract
const storageCount = await contractCaller.count()

// @notice since callcode use the code from `targetContract` to update `ContractCaller` contract
// => `storageCount` is expected to equal `COUNT_A`
expect(storageCount).to.eq(COUNT_A)
expect(callSetCountResult).to.be.true
})
})

0 comments on commit ce381d4

Please sign in to comment.