您的位置:首页 > 房产 > 家装 > 【Solidity】合约交互基础

【Solidity】合约交互基础

2025/3/10 14:08:35 来源:https://blog.csdn.net/Superman_H/article/details/141402854  浏览:    关键词:【Solidity】合约交互基础

数据的编码与解码

数据编码:

  1. abi.encode:每个参数都会被填充为 32 字节的数据,并拼接在一起

  2. abi.encodePacked:类似 abi.encode,但会省略其中填充的零

解码:

  1. abi.decode:接受两个参数:编码后的数据和类型列表
contract ABIExample {function encodeData() external pure returns (bytes memory) {uint a = 1;address b = 0x1234567890123456789012345678901234567890;string memory c = "Hello, World!";return abi.encode(a, b, c);}function decodeData(bytes memory data) external pure returns (uint, address, string memory) {(uint a, address b, string memory c) = abi.decode(data,(uint, address, string));return (a, b, c);}
}



函数签名 & 函数选择器

function transfer(address recipient, uint amount) external returns (bool);

函数签名:由函数名称和参数类型组成,中间没有空格 - transfer(address,uint)

函数选择器

  1. 为函数签名通过 Keccak-256 哈希计算得到的前 4 个字节 - bytes4(keccak256("transfer(address,uint)"))
  2. 如果不想手动编写函数签名,可以使用 Solidity 的内置函数 this.functionName.selector 来直接获取函数选择器



函数的编码

  1. abi.encodeWithSignature:第一个参数为函数签名,后面的参数为函数参数

  2. abi.encodeWithSelector:第一个参数为函数选择器,后面的参数为函数参数

contract TargetContract {uint public value;string public message;function setValue(uint _value, string memory _message) public {value = _value;message = _message;}
}contract CallerContract {function callSetValue1(address target,uint _value,string memory _message) public {// 获取函数签名string memory signature = "setValue(uint256,string)";// 通过 encodeWithSignature 编码函数签名及传入的参数(bool success, ) = target.call(abi.encodeWithSignature(signature, _value, _message));require(success, "Call failed");}function callSetValue2(address target,uint _value,string memory _message) public {// 函数选择器 (方法 1)bytes4 selector1 = bytes4(keccak256("setValue(uint256,string)"));// 函数选择器 (方法 2)bytes4 selector2 = TargetContract(target).setValue.selector;// 通过 encodeWithSelector 编码函数选择器及传入的参数(bool success, ) = target.call(abi.encodeWithSelector(selector1, _value, _message));require(success, "Call failed");}
}



直接调用其他合约的方法

contract Demo1 {// 方法 1: 通过地址调用function setDemo2X_1(address _demo2, uint _x) public {Demo2 demo2 = Demo2(_demo2);demo2.setX(_x);}// 方法 2: 通过合约实例调用function setDemo2X_2(Demo2 _demo2, uint _x) public {_demo2.setX(_x);}
}contract Demo2 {uint public x;event Log(address caller, uint x);function setX(uint _x) public {x = _x;emit Log(msg.sender, x);}
}
  1. 部署 Demo2 合约

  2. 调用 Demo2 合约的 setX 方法,设置 x 值;查看 Demo2 合约的 x 值,可以看到 x 值被更新;查看 Log 事件,可以看到调用者地址为编辑器地址

  3. 部署 Demo1 合约

  4. 传入 Demo1 合约的地址和新 x 值,调用 Demo1 合约的 setDemo2X_1 方法;查看 Demo2 合约的 x 值,可以看到 x 值被更新;查看 Log 事件,可以看到调用者地址为 Demo1 合约地址

  5. 传入 Demo1 合约的地址和新 x 值,调用 Demo1 合约的 setDemo2X_2 方法;查看 Demo2 合约的 x 值,可以看到 x 值被更新;查看 Log 事件,可以看到调用者地址为 Demo1 合约地址


可以在调用的同时传输以太币:

contract Demo1 {function setDemo2X(Demo2 _demo2, uint _x) public payable {_demo2.setX{value: msg.value}(_x); // 要求: msg.value >= value 值;  这里设置成一样}
}contract Demo2 {uint public x;uint public value;address public caller;function setX(uint _x) public payable {value = msg.value;caller = msg.sender;x = _x;}
}
  1. 部署 Demo2 合约

  2. 传入新 x 值,设置以太币数量,调用 Demo2 合约的 setX 方法;查看 Demo2 合约的 x 值、value 值、caller 值,可以看到 x 值被更新、value 值为设置的以太币数量、caller 值为编辑器地址

  3. 部署 Demo1 合约

  4. 传入 Demo2 合约的地址、新 x 值,设置以太币数量,调用 Demo1 合约的 setDemo2X 方法;查看 Demo2 合约的 x 值、value 值、caller 值,可以看到 x 值被更新、value 值为设置的以太币数量、caller 值为 Demo1 合约地址



Interface

接口(Interface)用于定义合约之间的交互标准,确保不同合约之间可以互操作。

  1. 接口不能定义状态变量
  2. 接口只声明函数的签名,而不包含函数的实现;所有函数必须声明为 external;不能包含构造函数
  3. 接口可以继承其他接口,但不能继承合约。

现有如下合约交互:

contract Counter {uint public count;function increment() public {count += 1;}
}contract MyContract {// 通过地址调用 Counter 合约的 increment 方法function incrementCounter(address _counter) public {Counter(_counter).increment();}// 通过地址获取 Counter 合约的状态变量 countfunction getCount(address _counter) public view returns (uint) {return Counter(_counter).count();}
}

使用接口:

interface ICounter {function increment() external;function count() external view returns (uint);
}contract MyContract {function incrementCounter(address _counter) public {ICounter(_counter).increment();}function getCount(address _counter) public view returns (uint) {return ICounter(_counter).count();}
}



通过 call 方法调用其他合约的方法

call 是一个比较底层的方法,可以用来调用其他合约的函数 同时发送以太。

contract TestCall {event Log(string _str, uint _num, uint _value, address _sender);function foo(string calldata _str,uint _num) external payable returns (string memory, uint) {emit Log(_str, _num, msg.value ,msg.sender);return (_str, _num);}
}contract Call {bytes public data;function testCall(address _addr) public payable {(bool success, bytes memory _data) = _addr.call{// 传输的以太数量; 若设置的以太数量小于该下限, 会报错value: 100,// gas 上限; 若消耗的 gas 大于该上限, 会报错gas: 500000}(// 传入 encodeWithSignature 包装后的调用数据; 第 1 参数是方法签名, 不能有空格, 不能用简写abi.encodeWithSignature("foo(string,uint256)", "call foo", 123));require(success, "call failed");data = _data; // 返回值 _data 是被调用合约的方法的返回值}
}
  1. 部署 TestCall 合约

  2. 传入字符串和数字,设置以太币数量,调用 TestCall 合约的 foo 方法;查看 Log 事件,可以看到传入的字符串、数字、以太币数量、调用者地址 (为编辑器地址)

  3. 部署 Call 合约

  4. 传入 TestCall 合约的地址,设置以太币数量,调用 Call 合约的 testCall 方法;查看 Call 合约的 data 值,可以看到 TestCall 合约的 foo 方法的返回值 (为 bytes 形式);查看 Log 事件,可以看到传入的字符串、数字、以太币数量、调用者地址 (为 Call 合约地址)



版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com