Solidity合约调合约那些事
笔者记录这些问题的原因:solidity更新很快,才⼏个⽉没使⽤,现在使⽤最新版(0.5.10)使⽤call调⽤合约的时候,发现⼤变样。。。⽂章⽬录
合约调⽤合约,⼤体分为两种:
⼀、明确接⼝直接调⽤。
⼆、通⽤型调⽤。
⼀、接⼝直接调⽤
这种⽅式调⽤,是最简单⽅便的调⽤⽅式,缺点就是这能调⽤固定的接⼝,不够灵活。
先直接上代码。⽰例都是调⽤⼀个已经部署的合约的deposit⽅法。
pragma solidity ^0.5.10;
interface ContractInterface {
function deposit(string calldata _name) external payable returns(bool);
}
contract InterfaceCall {
function callDeposit(address _contract, string memory _args)
public
returns(bool)
{
ContractInterface ci = ContractInterface(_contract);
bool retValue = ci.deposit(_args);
return retValue;
}
}
⼆、通⽤型调⽤
通⽤型调⽤⼀般直接使⽤call⽅法调⽤。这种⽅式调⽤,是灵活的调⽤⽅式,缺点就是太灵活导致⽣产环境中出现了很多重⼤bug,使⽤此种调⽤⽅式,请明确风险。
先上代码。
警告: ⽣成环境中⼀定要处理call的返回值⽣成环境中⼀定要处理call的返回值!!⽣成环境中⼀定要处理call的返回值!!重要的事情说3遍。。。
pragma solidity ^0.5.10;
contract CallContract {
/**
* @dev ⽆限制调⽤指定合约的⽅法。
* @param _contract address 被调⽤的合约的部署地址
* @param _func string ⽅法的声明
* @param _args string ⽅法的参数
*/
function callFunc(address _contract, string memory _func, string memory _args)
public
returns(bytes memory)
{
solidity// 1. 获取函数的签名
bytes4 signature = bytes4(dePacked(_func)));
// 2. 把函数签名和参数通过 encodeWithSelector 压缩成⼀个 bytes
bytes memory _calldata = deWithSelector(signature, _args);
// 3. 调⽤函数的⽅法
(bool success, bytes memory returnData) = _contract.call(_calldata);
// 4. 处理返回值。
require(success == true, "call failure");
return returnData;
}
}
(1) 获取函数签名
⽅法⼀:
直接从Remix编译结果中取。 编译界⾯的Compilation Details 中的 FUNCTIONHASHES
// "a26e1186": "deposit(string)",
bytes4 selector = 0xa26e1186;
⽅法⼆:
利⽤abi编码⽅法计算。
function calculateSign(string memory _func) public pure returns(bytes4) {
return bytes4(dePacked(_func)));
}
原理:函数签名是函数声明的Keccak-256计算的前4个字节。
函数声明格式: 函数名字(参数类型1,参数类型N) ,和参数名称⽆关。
通过keccak256(bytes)⽅法对函数声明进⾏计算,取前4个字节,就是我们需要的函数签名。因为此hash函数的参数是bytes,所有我们先需要通过Packed把string类型转换成bytes类型
(2) 打包签名和参数
由于call(bytes calldata) returns(bool, bytes)函数⽅法的改变,过去直接传递函数签名和参数的⽅法不再可⾏。需要先通
过deWithSelector(byte4 selector, ...)把函数签名和参数编码成bytes类型
bytes memory _calldata = deWithSelector(selector, _args);
也可以把第⼀步和第⼆步合起来写,使⽤另外⼀个内置⽅法encodeWithSignature直接把函数声明和参数直接打包成⼀个bytes:
/**
* @dev ⽆限制调⽤指定合约的⽅法。
* @param _contract address 被调⽤的合约的部署地址
* @param _func string ⽅法的声明
* @param _args string ⽅法的参数
*/
function callFunc(address _contract, string memory _func, string memory _args)
public
returns(bytes memory)
{
bytes memory _calldata = deWithSignature(_func, _args);
(bool success, bytes memory returnData) = _contract.call(_calldata);
require(success == true, "call failure");
return returnData;
}
(3) 调⽤合约
现在可以通过这种⽅式调⽤:
(bool success, bytes memory returnData) = _contract.call(_calldata);
(4) 处理返回值
⽰例中只是简单的处理⼀下返回值,此处是以太坊智能合约的⼀个⼤坑。请⼀定要处理call的返回值。
三、安全问题
(1) 限制合约调⽤
有些情况下,要限制调⽤的⾝份,不让合约调⽤,只允许普通账户调⽤接⼝,避免以下漏洞和薅⽺⽑的⾏为。只通过查看地址的代码空间的长度来判断,是不能防⽌合约调⽤的(可以在部署合约的构造函数中调⽤,此时它的代码空间还是0,可以跳过这个限制)。
modifier isHuman() {
address _addr = msg.sender;
uint256 _codeLength;
assembly {_codeLength := extcodesize(_addr)}
require(_codeLength == 0, "sorry humans only");
require(_addr == tx.origin);
_;
}
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论