合约交互与 ABI
学习目标
掌握合约间调用方式、接口定义、ABI 数据结构、Web3.js 访问合约的方法。
合约间调用基础
- EOA(外部账号)发起调用,可能触发合约间的调用链
- 调用者必须持有被调用合约的地址
方式一:同文件内直接调用
当两个合约在同一个文件中时,可以直接通过合约类型和地址进行调用:
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.2 <0.9.0;
contract Callee {
uint256 public x;
function setX(uint _x) public {
x = _x;
}
}
contract Caller {
address calleeAddress;
constructor(address _callee) {
calleeAddress = _callee;
}
function setCalleeX(uint _x) public {
Callee callee = Callee(calleeAddress);
callee.setX(_x);
}
}
方式二:跨文件 import 调用
当合约分布在不同文件中时,使用 import 引入:
// 文件: Callee.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.2 <0.9.0;
contract Callee {
uint256 public x;
function setX(uint _x) public {
x = _x;
}
}
// 文件: Caller.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.2 <0.9.0;
import "contracts/Callee.sol";
contract Caller {
address calleeAddress;
constructor(address _callee) {
calleeAddress = _callee;
}
function setCalleeX(uint _x) public {
Callee callee = Callee(calleeAddress);
callee.setX(_x);
}
}
方式三:通过接口调用
接口(Interface)等价于 ABI,不依赖被调用合约的源码,只需知道函数签名即可调用。
定义接口
// 文件: ICallee.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.2 <0.9.0;
interface ICallee {
function setX(uint _x) external;
}
实现接口(不强制)
// 文件: Callee.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.2 <0.9.0;
import "contracts/ICallee.sol";
contract Callee is ICallee {
uint256 public x;
function setX(uint _x) public {
x = _x;
}
}
通过接口调用
// 文件: Caller.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.2 <0.9.0;
import "contracts/ICallee.sol";
contract Caller {
address calleeAddress;
constructor(address _callee) {
calleeAddress = _callee;
}
function setCalleeX(uint _x) public {
ICallee callee = ICallee(calleeAddress);
callee.setX(_x);
}
}
实际案例:访问 USDT 合约
只需知道合约地址和接口,就能与链上已部署的合约交互。
- USDT 合约地址:
0xdAC17F958D2ee523a2206206994597C13D831ec7
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.2 <0.9.0;
interface IERC20 {
function name() external view returns(string memory);
function symbol() external view returns(string memory);
function decimals() external view returns(uint8);
}
通过接口 + 地址即可读取 USDT 的名称、符号和精度,无需获取 USDT 的源码。
ABI(Application Binary Interface)
ABI 是合约的二进制接口描述,包含以下特征:
- 只有函数签名信息,没有实现代码
- EOA 通过 ABI 调用合约,合约之间通过接口调用
- 编译合约时自动生成
ABI JSON 示例
{
"abi": [
{
"inputs": [
{
"internalType": "uint256",
"name": "_x",
"type": "uint256"
}
],
"name": "setX",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
}
]
}
Web3.js 合约访问
Web3.js 是浏览器端调用合约的 JavaScript 库,支持两种引入方式:
- 直接引用
<script>标签 - Node.js 模块(
npm install web3)
使用步骤
- 导入 Web3.js
- 调起 MetaMask 登录,获取用户账户地址
- 用 ABI + 合约地址生成 contract 对象
- 调用合约方法:
- 读操作(view/pure):
.methods.funcName().call() - 写操作(修改状态):
.methods.funcName().send({ from: account })
代码示例
// 1. 导入 Web3(浏览器环境,MetaMask 注入 window.ethereum)
const web3 = new Web3(window.ethereum);
// 2. 请求 MetaMask 授权登录
const accounts = await window.ethereum.request({
method: "eth_requestAccounts"
});
const account = accounts[0];
// 3. 用 ABI + 合约地址创建 contract 对象
const abi = [ /* 合约 ABI JSON */ ];
const contractAddress = "0x1234...abcd";
const contract = new web3.eth.Contract(abi, contractAddress);
// 4a. 读操作(view 函数,不消耗 gas)
const value = await contract.methods.getX().call();
console.log("当前值:", value);
// 4b. 写操作(修改状态,消耗 gas,需要签名)
await contract.methods.setX(42).send({ from: account });
主题测试文章,只做测试使用。发布者:Walker,转转请注明出处:https://walker-learn.xyz/archives/7492
