Ganache+Web3+Solidity实现智能合约调用案例

主要步骤

  1. 准备: 启动节点 → 连接Web3 → 获取账户
  2. 编译: 读取源码 → 编译 → 提取ABI和字节码
  3. 部署: 构造合约 → 估算gas → 发送交易 → 等待确认 → 获取地址
  4. 初始化: 构造函数执行 → 状态变量设置 → 白名单初始化
  5. 交互准备: 实例化合约对象 → 测试只读函数
  6. 用户操作: 注册用户 → 存款ETH → 验证权限
  7. 权限管理: owner更新白名单 → 用户获得权限
  8. 状态修改: 用户调用写入函数 → gas消耗 → 状态更新
  9. 事件处理: 实时监听 → 历史查询 → 日志解析
  10. 批量操作: 批量更新 → 节省gas
  11. 错误处理: 捕获异常 → 重试机制 → 用户反馈
  12. 监控: Gas跟踪 → 事件日志 → 状态变化

MyContract.sol 合约

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

contract MyContract {
    // 状态变量
    address public owner;
    uint256 public value;
    string public name;
    bool public isActive;
    
    // 结构体
    struct User {
        uint256 id;
        string name;
        uint256 balance;
        uint256 createdAt;
    }
    
    // 映射
    mapping(address => uint256) public balances;
    mapping(address => User) public users;
    mapping(address => bool) public whitelist;
    
    // 数组
    address[] public userAddresses;
    
    // 事件
    event ValueChanged(address indexed changer, uint256 oldValue, uint256 newValue);
    event UserRegistered(address indexed user, uint256 userId, string userName);
    event FundsDeposited(address indexed from, uint256 amount);
    event FundsWithdrawn(address indexed to, uint256 amount);
    event WhitelistUpdated(address indexed user, bool status);
    
    // 修饰器
    modifier onlyOwner() {
        require(msg.sender == owner, "Only owner can call this function");
        _;
    }
    
    modifier onlyWhitelisted() {
        require(whitelist[msg.sender] || msg.sender == owner, "Not whitelisted");
        _;
    }
    
    modifier whenActive() {
        require(isActive, "Contract is not active");
        _;
    }
    
    // 构造函数
    constructor(string memory _name, uint256 _initialValue) {
        owner = msg.sender;
        name = _name;
        value = _initialValue;
        isActive = true;
        
        // 将部署者加入白名单
        whitelist[msg.sender] = true;
    }
    
    // 1. 读取函数示例
    function getContractInfo() public view returns (
        address contractOwner,
        string memory contractName,
        uint256 contractValue,
        bool activeStatus,
        uint256 userCount
    ) {
        return (owner, name, value, isActive, userAddresses.length);
    }
    
    function getUserBalance(address _user) public view returns (uint256) {
        return balances[_user];
    }
    
    function getAllUsers() public view returns (address[] memory) {
        return userAddresses;
    }
    
    function calculateWithFee(uint256 _amount) public pure returns (uint256) {
        return _amount + (_amount * 5 / 100); // 5% 手续费
    }
    
    // 2. 写入函数示例
    function setValue(uint256 _newValue) public onlyWhitelisted whenActive {
        require(_newValue > 0, "Value must be greater than 0");
        
        uint256 oldValue = value;
        value = _newValue;
        
        emit ValueChanged(msg.sender, oldValue, _newValue);
    }
    
    function registerUser(string memory _userName) public whenActive returns (uint256) {
        require(bytes(_userName).length > 0, "Username cannot be empty");
        require(users[msg.sender].id == 0, "User already registered");
        
        uint256 userId = userAddresses.length + 1;
        
        users[msg.sender] = User({
            id: userId,
            name: _userName,
            balance: 0,
            createdAt: block.timestamp
        });
        
        userAddresses.push(msg.sender);
        
        emit UserRegistered(msg.sender, userId, _userName);
        return userId;
    }
    
    function deposit() public payable whenActive {
        require(msg.value > 0, "Deposit amount must be greater than 0");
        
        balances[msg.sender] += msg.value;
        
        emit FundsDeposited(msg.sender, msg.value);
    }
    
    function withdraw(uint256 _amount) public whenActive {
        require(balances[msg.sender] >= _amount, "Insufficient balance");
        require(_amount > 0, "Amount must be greater than 0");
        
        balances[msg.sender] -= _amount;
        
        // 使用call进行转账(推荐方式)
        (bool success, ) = msg.sender.call{value: _amount}("");
        require(success, "Transfer failed");
        
        emit FundsWithdrawn(msg.sender, _amount);
    }
    
    function transfer(address _to, uint256 _amount) public whenActive {
        require(balances[msg.sender] >= _amount, "Insufficient balance");
        require(_to != address(0), "Invalid recipient address");
        require(_amount > 0, "Amount must be greater than 0");
        
        balances[msg.sender] -= _amount;
        balances[_to] += _amount;
    }
    
    // 3. 仅owner可调用的函数
    function updateWhitelist(address _user, bool _status) public onlyOwner {
        whitelist[_user] = _status;
        emit WhitelistUpdated(_user, _status);
    }
    
    function toggleActive() public onlyOwner {
        isActive = !isActive;
    }
    
    function changeName(string memory _newName) public onlyOwner {
        name = _newName;
    }
    
    function withdrawContractBalance() public onlyOwner {
        uint256 contractBalance = address(this).balance;
        require(contractBalance > 0, "No balance to withdraw");
        
        (bool success, ) = owner.call{value: contractBalance}("");
        require(success, "Transfer failed");
    }
    
    // 4. 批量操作
    function batchUpdateWhitelist(address[] memory _users, bool[] memory _statuses) public onlyOwner {
        require(_users.length == _statuses.length, "Arrays length mismatch");
        
        for (uint256 i = 0; i < _users.length; i++) {
            whitelist[_users[i]] = _statuses[i];
            emit WhitelistUpdated(_users[i], _statuses[i]);
        }
    }
    
    // 5. 工具函数
    function getUserInfo(address _user) public view returns (
        uint256 userId,
        string memory userName,
        uint256 userBalance,
        uint256 registrationTime,
        bool isWhitelisted
    ) {
        User memory user = users[_user];
        return (
            user.id,
            user.name,
            balances[_user],
            user.createdAt,
            whitelist[_user]
        );
    }
    
    function getContractBalance() public view returns (uint256) {
        return address(this).balance;
    }
    
    // 6. 回退函数和接收函数
    receive() external payable {
        balances[msg.sender] += msg.value;
        emit FundsDeposited(msg.sender, msg.value);
    }
    
    fallback() external payable {
        // 自定义回退逻辑
        if (msg.value > 0) {
            balances[msg.sender] += msg.value;
            emit FundsDeposited(msg.sender, msg.value);
        }
    }
    
    // 7. 权限转移
    function transferOwnership(address _newOwner) public onlyOwner {
        require(_newOwner != address(0), "Invalid new owner address");
        owner = _newOwner;
    }
}

完整的使用示例代码

// myContractInteractions.js
const Web3 = require('web3');
const fs = require('fs');
const solc = require('solc');

// 1. 初始化Web3
const web3 = new Web3('http://localhost:8545');

class MyContractInteractor {
    constructor() {
        this.contract = null;
        this.accounts = [];
        this.contractAddress = '';
    }
    
    // 2. 编译合约
    async compileContract() {
        const source = fs.readFileSync('MyContract.sol', 'utf8');
        
        const input = {
            language: 'Solidity',
            sources: {
                'MyContract.sol': {
                    content: source
                }
            },
            settings: {
                outputSelection: {
                    '*': {
                        '*': ['abi', 'evm.bytecode']
                    }
                }
            }
        };
        
        const output = JSON.parse(solc.compile(JSON.stringify(input)));
        this.abi = output.contracts['MyContract.sol']['MyContract'].abi;
        this.bytecode = output.contracts['MyContract.sol']['MyContract'].evm.bytecode.object;
        
        console.log('✅ 合约编译成功');
        return { abi: this.abi, bytecode: this.bytecode };
    }
    
    // 3. 部署合约
    async deployContract() {
        try {
            this.accounts = await web3.eth.getAccounts();
            
            const contract = new web3.eth.Contract(this.abi);
            
            console.log('🚀 部署合约中...');
            const deployedContract = await contract.deploy({
                data: '0x' + this.bytecode,
                arguments: ['MyFirstContract', 100] // 构造函数参数
            })
            .send({
                from: this.accounts[0],
                gas: 3000000,
                gasPrice: web3.utils.toWei('20', 'gwei')
            });
            
            this.contractAddress = deployedContract.options.address;
            this.contract = deployedContract;
            
            console.log(`✅ 合约部署成功,地址: ${this.contractAddress}`);
            console.log(`📝 交易哈希: ${deployedContract.transactionHash}`);
            
            return deployedContract;
        } catch (error) {
            console.error('❌ 部署失败:', error);
            throw error;
        }
    }
    
    // 4. 连接到已部署的合约
    async connectToContract(address) {
        this.contractAddress = address;
        this.contract = new web3.eth.Contract(this.abi, address);
        console.log(`✅ 已连接到合约: ${address}`);
        return this.contract;
    }
    
    // 5. 读取函数示例
    async readContractInfo() {
        try {
            console.log('\n📖 读取合约信息...');
            
            // 读取公共变量
            const owner = await this.contract.methods.owner().call();
            const contractName = await this.contract.methods.name().call();
            const value = await this.contract.methods.value().call();
            const isActive = await this.contract.methods.isActive().call();
            
            console.log(`👑 合约所有者: ${owner}`);
            console.log(`🏷️  合约名称: ${contractName}`);
            console.log(`💰 当前值: ${value}`);
            console.log(`🔌 激活状态: ${isActive}`);
            
            // 调用返回多个值的函数
            const info = await this.contract.methods.getContractInfo().call();
            console.log(`👥 用户数量: ${info.userCount}`);
            
            // 计算函数
            const withFee = await this.contract.methods.calculateWithFee(100).call();
            console.log(`🧮 100 + 5%手续费 = ${withFee}`);
            
            return { owner, contractName, value, isActive };
        } catch (error) {
            console.error('❌ 读取失败:', error);
        }
    }
    
    // 6. 写入函数示例
    async interactWithContract() {
        try {
            console.log('\n✍️  与合约交互...');
            
            // 注册用户
            console.log('👤 注册用户...');
            const tx1 = await this.contract.methods.registerUser("Alice").send({
                from: this.accounts[1],
                gas: 200000
            });
            console.log(`✅ 用户注册成功,交易哈希: ${tx1.transactionHash}`);
            
            // 存款
            console.log('💰 存款1 ETH...');
            const tx2 = await this.contract.methods.deposit().send({
                from: this.accounts[1],
                value: web3.utils.toWei('1', 'ether'),
                gas: 150000
            });
            console.log(`✅ 存款成功,交易哈希: ${tx2.transactionHash}`);
            
            // 查询余额
            const balance = await this.contract.methods.getUserBalance(this.accounts[1]).call();
            console.log(`💳 用户余额: ${web3.utils.fromWei(balance, 'ether')} ETH`);
            
            // 转账给其他用户
            console.log('🔄 转账给其他用户...');
            const tx3 = await this.contract.methods.transfer(this.accounts[2], web3.utils.toWei('0.5', 'ether'))
                .send({
                    from: this.accounts[1],
                    gas: 150000
                });
            console.log(`✅ 转账成功,交易哈希: ${tx3.transactionHash}`);
            
            // Owner操作:更新白名单
            console.log('📝 更新白名单...');
            const tx4 = await this.contract.methods.updateWhitelist(this.accounts[1], true)
                .send({
                    from: this.accounts[0],
                    gas: 150000
                });
            console.log(`✅ 白名单更新成功`);
            
            // 设置新值(现在用户在白名单中)
            console.log('🔧 设置新值...');
            const tx5 = await this.contract.methods.setValue(200)
                .send({
                    from: this.accounts[1],
                    gas: 150000
                });
            console.log(`✅ 值更新成功,交易哈希: ${tx5.transactionHash}`);
            
        } catch (error) {
            console.error('❌ 交互失败:', error.message);
        }
    }
    
    // 7. 事件监听
    async setupEventListeners() {
        console.log('\n👂 设置事件监听器...');
        
        // 监听ValueChanged事件
        this.contract.events.ValueChanged({
            filter: {},
            fromBlock: 0
        })
        .on('data', (event) => {
            console.log(`📊 ValueChanged事件:`);
            console.log(`   改变者: ${event.returnValues.changer}`);
            console.log(`   旧值: ${event.returnValues.oldValue}`);
            console.log(`   新值: ${event.returnValues.newValue}`);
            console.log(`   区块: ${event.blockNumber}`);
        })
        .on('error', console.error);
        
        // 监听UserRegistered事件
        this.contract.events.UserRegistered({
            filter: {},
            fromBlock: 0
        })
        .on('data', (event) => {
            console.log(`👤 UserRegistered事件:`);
            console.log(`   用户: ${event.returnValues.user}`);
            console.log(`   ID: ${event.returnValues.userId}`);
            console.log(`   名称: ${event.returnValues.userName}`);
        });
        
        // 监听所有事件
        this.contract.events.allEvents({
            fromBlock: 'latest'
        })
        .on('data', (event) => {
            console.log(`📨 收到事件: ${event.event} from ${event.returnValues}`);
        });
    }
    
    // 8. 批量操作示例
    async batchOperations() {
        console.log('\n🔄 批量操作示例...');
        
        try {
            // 批量更新白名单
            const users = [this.accounts[2], this.accounts[3]];
            const statuses = [true, true];
            
            const tx = await this.contract.methods
                .batchUpdateWhitelist(users, statuses)
                .send({
                    from: this.accounts[0],
                    gas: 300000
                });
                
            console.log(`✅ 批量操作成功,交易哈希: ${tx.transactionHash}`);
        } catch (error) {
            console.error('❌ 批量操作失败:', error);
        }
    }
    
    // 9. 查询历史事件
    async queryPastEvents() {
        try {
            console.log('\n📜 查询历史事件...');
            
            const events = await this.contract.getPastEvents('ValueChanged', {
                fromBlock: 0,
                toBlock: 'latest'
            });
            
            console.log(`📊 找到 ${events.length} 个ValueChanged事件`);
            events.forEach((event, index) => {
                console.log(`   ${index + 1}. ${event.returnValues.changer} 从 ${event.returnValues.oldValue} 改为 ${event.returnValues.newValue}`);
            });
            
            return events;
        } catch (error) {
            console.error('❌ 查询事件失败:', error);
        }
    }
    
    // 10. 完整的交互流程
    async fullInteraction() {
        try {
            // 编译合约
            await this.compileContract();
            
            // 部署或连接
            const useExisting = false; // 设为true使用已部署合约
            const existingAddress = '0x...'; // 已部署合约地址
            
            if (!useExisting) {
                await this.deployContract();
            } else {
                await this.connectToContract(existingAddress);
            }
            
            // 获取账户
            this.accounts = await web3.eth.getAccounts();
            console.log(`👥 可用账户: ${this.accounts.slice(0, 3).join(', ')}...`);
            
            // 读取合约信息
            await this.readContractInfo();
            
            // 设置事件监听
            await this.setupEventListeners();
            
            // 与合约交互
            await this.interactWithContract();
            
            // 批量操作
            await this.batchOperations();
            
            // 查询历史事件
            await this.queryPastEvents();
            
            console.log('\n🎉 所有操作完成!');
            
        } catch (error) {
            console.error('❌ 流程执行失败:', error);
        }
    }
}

// 使用示例
async function main() {
    const interactor = new MyContractInteractor();
    
    // 运行完整流程
    await interactor.fullInteraction();
    
    // 或单独调用某个功能
    // await interactor.compileContract();
    // await interactor.deployContract();
    // await interactor.connectToContract('0x...');
    // await interactor.readContractInfo();
}

// 运行主函数
if (require.main === module) {
    main().catch(console.error);
}

module.exports = MyContractInteractor;

package.json 依赖

{
  "name": "my-contract-interaction",
  "version": "1.0.0",
  "scripts": {
    "start": "node myContractInteractions.js",
    "deploy": "node deploy.js",
    "test": "node testInteractions.js"
  },
  "dependencies": {
    "web3": "^4.0.3",
    "solc": "^0.8.19",
    "dotenv": "^16.0.0"
  }
}

测试脚本示例

// testInteractions.js
const MyContractInteractor = require('./myContractInteractions.js');

async function test() {
    const interactor = new MyContractInteractor();
    
    // 测试编译
    await interactor.compileContract();
    
    // 测试部署(需要本地以太坊节点)
    if (process.env.TEST_DEPLOY === 'true') {
        await interactor.deployContract();
        await interactor.readContractInfo();
    }
    
    console.log('✅ 测试完成');
}

test();

这个完整的示例包含了:

  1. 全面的 Solidity 合约:包含各种函数类型、修饰器、事件、数据结构
  2. 完整的 Web3.js 交互流程:编译、部署、读取、写入、事件监听
  3. 错误处理和最佳实践
  4. 模块化设计:便于扩展和维护
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

友莘居士

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值