1. 跨链资产锁定与解锁基础合约
任务说明
本案例实现一个最小可用的跨链资产锁定合约(Solidity),核心能力为:锁定资产(用户将代币存入合约并记录锁定信息)与释放资产(仅合约管理员可解锁并退回代币),同时为每次锁定与释放操作发出链上事件,便于跨链监听与审计。合约不涉及真实跨链消息传递,聚焦于“锁定-释放”这一基础原语。
合约实现
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts//ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
/**
* @title CrossChainAssetVault
* @notice 跨链资产锁定与释放基础合约
* @dev 仅支持ERC20代币的锁定与释放,管理员(owner)负责释放操作
*/
contract CrossChainAssetVault is Ownable {
IERC20 public immutable ;
// 锁定记录:lockId => LockRecord
struct LockRecord {
address user; // 锁定资产的用户
uint256 amount; // 锁定数量
uint256 lockedAt; // 锁定时间戳
bool released; // 是否已释放
}
mapping(uint256 => LockRecord) public lockRecords;
uint256 public nextLockId = 1;
// 事件:锁定与释放(供跨链监听)
event Locked(uint256 indexed lockId, address indexed user, uint256 amount, uint256 lockedAt);
event Released(uint256 indexed lockId, address indexed user, uint256 amount, uint256 releasedAt);
constructor(address _) Ownable(msg.sender) {
require(_ != address(0), "invalid address");
= IERC20(_);
}
/**
* @notice 用户锁定代币到合约
* @param _amount 锁定数量
* @return lockId 生成的锁定记录ID
*/
function lock(uint256 _amount) external returns (uint256 lockId) {
require(_amount > 0, "amount must be > 0");
require(.transferFrom(msg.sender, address(this), _amount), "transferFrom failed");
lockId = nextLockId++;
lockRecords[lockId] = LockRecord({
user: msg.sender,
amount: _amount,
lockedAt: block.timestamp,
released: false
});
emit Locked(lockId, msg.sender, _amount, block.timestamp);
return lockId;
}
/**
* @notice 管理员释放指定锁定记录,将代币退回原用户
* @param _lockId 锁定记录ID
*/
function release(uint256 _lockId) external onlyOwner {
LockRecord storage record = lockRecords[_lockId];
require(record.amount > 0, "lock record not found");
require(!record.released, "already released");
record.released = true;
require(.transfer(record.user, record.amount), "transfer failed");
emit Released(_lockId, record.user, record.amount, block.timestamp);
}
/**
* @notice 查询锁定记录详情
*/
function getLockRecord(uint256 _lockId) external view returns (address user, uint256 amount, uint256 lockedAt, bool released) {
LockRecord storage r = lockRecords[_lockId];
return (r.user, r.amount, r.lockedAt, r.released);
}
}
关键设计说明
- 锁定流程:用户需先对合约执行
ERC20.approve,再调用lock(amount),合约通过transferFrom将代币划转至自身地址,并生成自增lockId记录。 - 释放流程:仅合约部署者(owner)可调用
release(lockId),将锁定代币退回原锁定用户,并标记该记录为已释放,防止重复释放。 - 事件监听:
Locked与Released事件携带lockId、用户地址、金额与时间戳,可作为跨链中继器或子链监听的数据源。
落地与验证
1. 依赖安装
npm init -y
npm install --save-dev hardhat @openzeppelin/contracts
2. 部署脚本(scripts/deploy.js)
const hre = require("hardhat");
async function main() {
// 使用一个测试ERC20地址(示例:本地部署的MockToken)
const mockToken = await hre.ethers.deployContract("MockERC20");
await mockToken.waitForDeployment();
const vault = await hre.ethers.deployContract("CrossChainAssetVault", [mockToken.target]);
await vault.waitForDeployment();
console.log("Vault deployed to:", vault.target);
console.log("MockToken deployed to:", mockToken.target);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
3. 测试用例(test/vault.test.js)
const { expect } = require("chai");
describe("CrossChainAssetVault", function () {
let vault, mockToken, owner, user;
beforeEach(async function () {
[owner, user] = await ethers.getSigners();
const MockERC20 = await ethers.getContractFactory("MockERC20");
mockToken = await MockERC20.deploy();
await mockToken.waitForDeployment();
const Vault = await ethers.getContractFactory("CrossChainAssetVault");
vault = await Vault.deploy(mockToken.target);
await vault.waitForDeployment();
// 给user铸造1000个代币并授权给vault
await mockToken.mint(user.address, 1000);
await mockToken.connect(user).approve(vault.target, 1000);
});
it("应锁定资产并发出Locked事件", async function () {
await expect(vault.connect(user).lock(500))
.to.emit(vault, "Locked")
.withArgs(1, user.address, 500, anyValue);
expect(await mockToken.balanceOf(vault.target)).to.equal(500);
});
it("仅owner可释放资产", async function () {
await vault.connect(user).lock(300);
await expect(vault.connect(user).release(1)).to.be.revertedWith("Ownable: caller is not the owner");
await expect(vault.release(1))
.to.emit(vault, "Released")
.withArgs(1, user.address, 300, anyValue);
expect(await mockToken.balanceOf(user.address)).to.equal(1000);
});
it("不可重复释放", async function () {
await vault.connect(user).lock(200);
await vault.release(1);
await expect(vault.release(1)).to.be.revertedWith("already released");
});
});
4. 运行验证
npx hardhat test
判定标准:三个测试全部通过,且事件参数(lockId、user、amount)与锁定/释放逻辑一致,即视为本案例实现正确。