-
Notifications
You must be signed in to change notification settings - Fork 54
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
1 parent
7864827
commit ce381d4
Showing
3 changed files
with
265 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
}) | ||
}) |