Solidity合约调合约那些事

笔者记录这些问题的原因:solidity更新很快,才几个月没使用,现在使用最新版(0.5.10)使用call调用合约的时候,发现大变样。。。

文章目录

    • 一、接口直接调用
    • 二、通用型调用
      • (1) 获取函数签名
      • (2) 打包签名和参数
      • (3) 调用合约
      • (4) 处理返回值
    • 三、安全问题
      • (1) 限制合约调用

合约调用合约,大体分为两种:

  • 一、明确接口直接调用。
  • 二、通用型调用。

一、接口直接调用

这种方式调用,是最简单方便的调用方式,缺点就是这能调用固定的接口,不够灵活。

先直接上代码。示例都是调用一个已经部署的合约的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) 
	{
		// 1. 获取函数的签名
		bytes4 signature = bytes4(keccak256(abi.encodePacked(_func)));
		// 2. 把函数签名和参数通过 encodeWithSelector 压缩成一个 bytes
		bytes memory _calldata = abi.encodeWithSelector(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(keccak256(abi.encodePacked(_func)));
}

原理:函数签名是函数声明的Keccak-256计算的前4个字节。

  • 函数声明格式: 函数名字(参数类型1,参数类型N) ,和参数名称无关。

通过keccak256(bytes)方法对函数声明进行计算,取前4个字节,就是我们需要的函数签名。因为此hash函数的参数是bytes,所有我们先需要通过abi.encoePacked把string类型转换成bytes类型

(2) 打包签名和参数

由于call(bytes calldata) returns(bool, bytes)函数方法的改变,过去直接传递函数签名和参数的方法不再可行。需要先通过abi.encodeWithSelector(byte4 selector, ...)把函数签名和参数编码成bytes类型

bytes memory _calldata = abi.encodeWithSelector(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 = abi.encodeWithSignature(_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);
        _;
    }

你可能感兴趣的:(区块链,Ethereum,Solidity)