Commit 2ff1117b authored by luxq's avatar luxq

update scripts

parent dbe81f33
node_modules
.env
# Hardhat files
/cache
/artifacts
# TypeChain files
/typechain
/typechain-types
# solidity-coverage files
/coverage
/coverage.json
# Hardhat Ignition default folder for deployments against a local node
ignition/deployments/chain-31337
.idea
# bridgecontract # bridgecontract
## environment
- node v20.18.1
```shell
npm i
```
## config
- `.env` set correct values for `DEPLOY_PRIVATE_KEY`, `BRIDGE_VALIDATORS`, `TREASURY`, in test environemnt, also set `TREASURY_PRIVATE_KEY`.
- `hardhat.config.js` set correct chain config.
## deploy contract to chain A and B
```shell
npx hardhat run ./scripts/deploy.js --network <network-A>
npx hardhat run ./scripts/deploy.js --network <network-B>
```
All deployment will write to `deploy.json` file in the current directory.
## set necessary parameters
```shell
npx hardhat run ./scripts/setconfig.js --network <network-A>
npx hardhat run ./scripts/setconfig.js --network <network-B>
```
## do test
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
/**
* @title IERC20Burnable
* @dev 扩展ERC20接口,添加销毁功能
*/
interface IERC20Burnable is IERC20 {
function burn(uint256 amount) external;
}
/**
* @title Bridge
* @dev 跨链桥合约,支持代币在不同区块链之间转移
* @notice 使用多签验证者机制确保跨链转移的安全性
*/
contract Bridge is Ownable {
/// @dev 执行转入操作所需的最少验证者确认数量
uint256 public validRequired;
/// @dev 记录地址是否为验证者
mapping(address => bool) public isValidator;
/**
* @dev 转出配置结构体
* @param receiveToken 在目标链上接收的代币合约地址
* @param fee 转移手续费
* @param limit 单次转移的最大金额限制
* @param isBurn 是否销毁源链上的代币(用于总供应量固定的代币)
* @param enabled 是否启用该转移路径
*/
struct OutConfig {
address receiveToken;
uint256 fee;
uint256 limit;
bool isBurn;
bool enabled;
}
/// @dev 转出配置映射:源链代币地址 => 目标链ID => 转出配置
mapping(address => mapping(uint256 => OutConfig)) public outConfiguration;
/// @dev 获取支持转出的代币列表:目标链ID => 代币地址数组
mapping(uint256 => address[]) public supportedTokensOut;
/// @dev 检查代币是否支持转出:目标链ID => 代币地址 => 是否支持
mapping(uint256 => mapping(address => bool)) public isTokenSupportedOut;
/// @dev 转出记录映射:转出ID => 转出信息
mapping(uint256 => OutInfo) public outTransfers;
/// @dev 转入记录映射:转入ID => 转入信息
mapping(uint256 => InInfo) public inTransfers;
/// @dev 获取转入ID映射:源链ID => 转出ID => 转入ID
mapping(uint256 => mapping(uint256 => uint256)) public getInId;
/// @dev 转出操作的自增ID计数器
uint256 public _outID;
/// @dev 转入操作的自增ID计数器
uint256 public _inID;
/// @dev 每种代币的累计转出总量
mapping(address => uint256) public outTotal;
/// @dev 每种代币的累计转入总量
mapping(address => uint256) public inTotal;
/// @dev 金库地址
address public treasury;
/**
* @dev 代币金库配置结构体
* @param minReserve 最小保留金额(合约必须保留的最少数量)
* @param reserveRatio 保留比例(基数10000,如5000表示50%)
* @param enabled 是否启用金库转移功能
*/
struct TreasuryConfig {
uint256 minReserve; // 最小保留金额
uint256 reserveRatio; // 保留比例(基数10000)
bool enabled; // 是否启用
}
/// @dev 每个代币的金库配置:代币地址 => 金库配置
mapping(address => TreasuryConfig) public treasuryConfigs;
/// @dev 每个代币转移到金库的累计总量
mapping(address => uint256) public treasuryTotal;
/**
* @dev 转出操作信息结构体
* @param outId 转出操作的唯一ID
* @param fromChainID 源链ID
* @param sender 发送者地址
* @param token 转出的代币合约地址
* @param amount 转出金额
* @param fee 手续费
* @param toChainID 目标链ID
* @param receiver 接收者地址
* @param receiveToken 在目标链上接收的代币合约地址
* @param receiveAmount 实际接收金额(扣除手续费后)
* @param signature 操作签名,用于验证
*/
struct OutInfo {
uint256 outId;
uint256 fromChainID;
address sender;
address token;
uint256 amount;
uint256 fee;
uint256 toChainID;
address receiver;
address receiveToken;
uint256 receiveAmount;
bytes32 signature;
}
/**
* @dev 转入操作信息结构体
* @param inId 转入操作的唯一ID
* @param token 转入的代币合约地址
* @param receiver 接收者地址
* @param fee 手续费
* @param amount 转入金额
* @param fromChainID 源链ID
* @param outId 对应的转出操作ID
* @param executed 是否已执行
* @param confirmCounter 确认数量计数器
* @param confirmations 验证者确认记录
* @param rejectCounter 拒绝数量计数器
* @param rejections 验证者拒绝记录
*/
struct InInfo {
uint256 inId;
address token;
address receiver;
uint256 fee;
uint256 amount;
uint256 fromChainID;
uint256 outId;
bool executed;
uint256 confirmCounter;
mapping(address => bool) confirmations;
uint256 rejectCounter;
mapping(address => bool) rejections;
}
// ============ 事件定义 ============
/**
* @dev 转出操作事件
* @param outId 转出ID
* @param fromChainID 源链ID
* @param sender 发送者
* @param token 代币地址
* @param amount 转出金额
* @param fee 手续费
* @param toChainID 目标链ID
* @param receiver 接收者
* @param receiveToken 接收代币地址
*/
event TransferOut(
uint256 outId,
uint256 fromChainID,
address sender,
address token,
uint256 amount,
uint256 fee,
uint256 toChainID,
address receiver,
address receiveToken
);
/**
* @dev 转入操作事件
* @param fromChainID 源链ID
* @param outId 对应的转出ID
* @param inId 转入ID
* @param receiver 接收者
* @param token 代币地址
* @param amount 转入金额
*/
event TransferIn(
uint256 fromChainID,
uint256 outId,
uint256 inId,
address receiver,
address token,
uint256 amount
);
/**
* @dev 转入确认事件
* @param inId 转入ID
* @param validator 确认的验证者
*/
event TransferInConfirmation(
uint256 inId,
address validator
);
/**
* @dev 转入拒绝事件
* @param inId 转入ID
* @param validator 拒绝的验证者
*/
event TransferInRejection(
uint256 inId,
address validator
);
/**
* @dev 转入执行事件
* @param inId 转入ID
* @param receiver 接收者
* @param token 代币地址
* @param amount 转入金额
*/
event TransferInExecution(
uint256 inId,
address receiver,
address token,
uint256 amount
);
/// @dev 代币配置变更事件
event TokenConfigChanged(address indexed token, uint256 indexed toChainID, bool enabled);
/// @dev 金库地址变更事件
event TreasuryChanged(address indexed oldTreasury, address indexed newTreasury);
/// @dev 金库配置变更事件
event TreasuryConfigChanged(address indexed token, uint256 minReserve, uint256 reserveRatio, bool enabled);
/// @dev 资金转移到金库事件
event TreasuryTransfer(address indexed token, uint256 amount, uint256 contractBalance, uint256 treasuryBalance);
/// @dev 验证者添加事件
event ValidatorAddition(address validator);
/// @dev 验证者移除事件
event ValidatorRemoval(address validator);
/// @dev 所需确认数变更事件
event RequirementChange(uint prior, uint256 present);
// ============ 构造函数 ============
/**
* @notice 初始化合约,设置初始所需确认数为1
* @param initialOwner 合约的初始所有者
*/
constructor(address initialOwner) Ownable(initialOwner) {
validRequired = 1;
treasury = address(0); // 初始化金库地址为0,需要后续设置
}
// ============ 验证者管理函数 ============
/**
* @dev 添加验证者
* @param validator 要添加的验证者地址
* @notice 只有合约所有者可以调用
*/
function addValidator(address validator) external onlyOwner {
isValidator[validator] = true;
emit ValidatorAddition(validator);
}
/**
* @dev 移除验证者
* @param validator 要移除的验证者地址
* @notice 只有合约所有者可以调用
*/
function removeValidator(address validator) external onlyOwner {
isValidator[validator] = false;
emit ValidatorRemoval(validator);
}
/**
* @dev 替换验证者
* @param validator 要被替换的验证者地址
* @param newValidator 新验证者地址
* @notice 只有合约所有者可以调用
* @notice 函数名存在拼写错误,应为replaceValidator
*/
function replaceValidator(address validator, address newValidator) external onlyOwner {
isValidator[validator] = false;
isValidator[newValidator] = true;
emit ValidatorAddition(newValidator);
emit ValidatorRemoval(validator);
}
// ============ 修饰符 ============
/**
* @dev 验证者权限修饰符
* @notice 确保调用者是验证者
*/
modifier onlyValidator {
require(isValidator[msg.sender], "not validator");
_;
}
/**
* @dev 非当前链修饰符
* @param chainID 要检查的链ID
* @notice 确保指定链ID不是当前链
*/
modifier notCurrentChain(uint256 chainID) {
uint256 id;
assembly {
id := chainid()
}
require(chainID != id, "can't current chain");
_;
}
/**
* @dev 当前链修饰符
* @param chainID 要检查的链ID
* @notice 确保指定链ID是当前链
*/
modifier currentChain(uint256 chainID) {
uint256 id;
assembly {
id := chainid()
}
require(chainID == id, "must current chain");
_;
}
// ============ 配置管理函数 ============
/**
* @dev 修改所需验证者确认数量
* @param required 新的所需确认数量
* @notice 只有合约所有者可以调用
*/
function changeValidRequired(uint256 required) external onlyOwner {
uint256 prior = validRequired;
validRequired = required;
emit RequirementChange(prior, required);
}
/**
* @dev 修改转出配置
* @param token 代币合约地址
* @param toChainID 目标链ID
* @param config 新的转出配置
* @notice 只有合约所有者可以调用
* @notice 目标链不能是当前链
*/
function changeOutConfig(address token, uint256 toChainID, OutConfig memory config) external onlyOwner notCurrentChain(toChainID) {
bool wasEnabled = outConfiguration[token][toChainID].enabled;
outConfiguration[token][toChainID] = config;
// 如果是新启用的代币,添加到支持列表
if (config.enabled && !wasEnabled) {
if (!isTokenSupportedOut[toChainID][token]) {
supportedTokensOut[toChainID].push(token);
isTokenSupportedOut[toChainID][token] = true;
}
}
// 如果是禁用代币,从支持列表移除
else if (!config.enabled && wasEnabled) {
_removeTokenFromArray(supportedTokensOut[toChainID], token);
isTokenSupportedOut[toChainID][token] = false;
}
emit TokenConfigChanged(token, toChainID, config.enabled);
}
/**
* @dev 设置金库地址
* @param newTreasury 新的金库地址
* @notice 只有合约所有者可以调用
*/
function setTreasury(address newTreasury) external onlyOwner {
require(newTreasury != address(0), "treasury cannot be zero address");
address oldTreasury = treasury;
treasury = newTreasury;
emit TreasuryChanged(oldTreasury, newTreasury);
}
/**
* @dev 设置代币的金库配置
* @param token 代币地址
* @param minReserve 最小保留金额
* @param reserveRatio 保留比例(基数10000,如5000表示50%)
* @param enabled 是否启用金库功能
* @notice 只有合约所有者可以调用
*/
function setTreasuryConfig(
address token,
uint256 minReserve,
uint256 reserveRatio,
bool enabled
) external onlyOwner {
require(reserveRatio <= 10000, "reserve ratio cannot exceed 100%");
require(minReserve > 0 || !enabled, "min reserve must be positive when enabled");
treasuryConfigs[token] = TreasuryConfig({
minReserve: minReserve,
reserveRatio: reserveRatio,
enabled: enabled
});
emit TreasuryConfigChanged(token, minReserve, reserveRatio, enabled);
}
/**
* @dev 检查并自动转移资金到金库
* @param token 代币地址
* @notice 任何人都可以调用,触发金库转移检查
*/
function autoTransferToTreasury(address token) public {
require(treasury != address(0), "treasury not set");
TreasuryConfig memory config = treasuryConfigs[token];
require(config.enabled, "treasury transfer not enabled for token");
uint256 currentBalance = IERC20(token).balanceOf(address(this));
// 计算按比例应该保留的金额
uint256 reserveByRatio = (currentBalance * config.reserveRatio) / 10000;
// 使用较大的保留金额(比例保留 vs 最小保留)
uint256 actualReserve = reserveByRatio > config.minReserve ? reserveByRatio : config.minReserve;
// 只有当比例保留金额大于等于最小保留金额时才转移
if (reserveByRatio >= config.minReserve && currentBalance > actualReserve) {
uint256 transferAmount = currentBalance - actualReserve;
require(IERC20(token).transfer(treasury, transferAmount), "treasury transfer failed");
treasuryTotal[token] += transferAmount;
emit TreasuryTransfer(token, transferAmount, actualReserve, treasuryTotal[token]);
}
}
/**
* @dev 计算代币的金库转移信息
* @param token 代币地址
* @return canTransfer 是否可以转移
* @return transferAmount 可转移金额
* @return willReserve 将保留的金额
* @return currentBalance 当前余额
*/
function calculateTreasuryTransfer(address token)
external
view
returns (
bool canTransfer,
uint256 transferAmount,
uint256 willReserve,
uint256 currentBalance
)
{
TreasuryConfig memory config = treasuryConfigs[token];
currentBalance = IERC20(token).balanceOf(address(this));
if (!config.enabled || treasury == address(0)) {
return (false, 0, currentBalance, currentBalance);
}
uint256 reserveByRatio = (currentBalance * config.reserveRatio) / 10000;
willReserve = reserveByRatio > config.minReserve ? reserveByRatio : config.minReserve;
canTransfer = reserveByRatio >= config.minReserve && currentBalance > willReserve;
transferAmount = canTransfer ? currentBalance - willReserve : 0;
}
/**
* @dev 内部函数:从数组中移除指定代币
* @param tokenArray 代币地址数组
* @param token 要移除的代币地址
*/
function _removeTokenFromArray(address[] storage tokenArray, address token) internal {
for (uint256 i = 0; i < tokenArray.length; i++) {
if (tokenArray[i] == token) {
tokenArray[i] = tokenArray[tokenArray.length - 1];
tokenArray.pop();
break;
}
}
}
/**
* @dev 执行转出操作
* @param token 要转出的代币合约地址
* @param amount 转出数量
* @param toChainID 目标链ID
* @param receiver 目标链上的接收者地址
* @notice 用户调用此函数将代币转移到其他链
* @notice 会收取配置的手续费,并检查转移限额
*/
function outTransfer(address token, uint256 amount, uint256 toChainID, address receiver)
external
notCurrentChain(toChainID)
{
// 获取转出配置
OutConfig storage config = outConfiguration[token][toChainID];
require(config.receiveToken != address(0), "not allow transfer");
require(config.enabled, "token transfer disabled");
require(amount > config.fee, "less than fee");
require(amount <= config.limit, "more than limit");
// 转移代币到合约
require(IERC20(token).transferFrom(msg.sender, address(this), amount), "transfer err");
// 如果配置为销毁模式,销毁代币
if (config.isBurn) {
IERC20Burnable(token).burn(amount);
}
// 获取当前链ID
uint256 fromChainID;
assembly {
fromChainID := chainid()
}
// 生成新的转出ID
_outID++;
uint256 outId = _outID;
// 记录转出信息
OutInfo storage info = outTransfers[outId];
info.outId = outId;
info.fromChainID = fromChainID;
info.sender = msg.sender;
info.token = token;
info.amount = amount;
info.fee = config.fee;
info.toChainID = toChainID;
info.receiver = receiver;
info.receiveToken = config.receiveToken;
info.receiveAmount = amount - config.fee;
// 生成签名用于验证
info.signature = keccak256(abi.encode(_outID, fromChainID, msg.sender, token, amount, toChainID, receiver, config.receiveToken));
// 更新转出总量
outTotal[token] = outTotal[token] + amount;
// 触发转出事件
emit TransferOut(outId, fromChainID, msg.sender, token, amount, info.fee, toChainID, receiver, config.receiveToken);
// 自动检查金库转移
if (treasury != address(0) && treasuryConfigs[token].enabled) {
autoTransferToTreasury(token);
}
}
/**
* @dev 提交转入操作参数结构体
* @param toChainID 目标链ID
* @param receiver 接收者地址
* @param token 目标链代币地址
* @param amount 转入金额
* @param outId 对应的转出操作ID
* @param fromChainID 源链ID
* @param sender 原始发送者地址
* @param sendToken 源链代币地址
* @param sendAmount 源链转出金额
* @param signature 转出操作签名
*/
struct submitParams {
uint256 toChainID;
address receiver;
address token;
uint256 amount;
uint256 outId;
uint256 fromChainID;
address sender;
address sendToken;
uint256 sendAmount;
bytes32 signature;
}
/**
* @dev 验证者提交转入操作
* @param params 转入操作参数
* @notice 验证者监听其他链的转出事件后调用此函数
* @notice 会验证签名并创建或更新转入记录
*/
function submitInTransfer(submitParams memory params)
external
onlyValidator
notCurrentChain(params.fromChainID)
currentChain(params.toChainID)
{
// 验证签名
bytes32 _signature = keccak256(abi.encode(params.outId, params.fromChainID, params.sender, params.sendToken, params.sendAmount, params.toChainID, params.receiver, params.token));
require(params.signature == _signature, "signature not equal");
// 检查是否已存在对应的转入记录
uint256 inId = getInId[params.fromChainID][params.outId];
if (inId == 0) {
// 创建新的转入记录
_inID++;
inId = _inID;
InInfo storage info = inTransfers[inId];
info.inId = inId;
info.token = params.token;
info.receiver = params.receiver;
info.amount = params.amount;
info.fromChainID = params.fromChainID;
info.outId = params.outId;
// 建立映射关系
getInId[params.fromChainID][params.outId] = inId;
// 触发转入事件
emit TransferIn(params.fromChainID, params.outId, inId, params.receiver, params.token, params.amount);
} else {
// 验证现有记录的一致性
require(inTransfers[inId].token == params.token, "token not equal");
require(inTransfers[inId].receiver == params.receiver, "receiver not equal");
require(inTransfers[inId].amount == params.amount, "amount not equal");
}
// 自动确认该转入操作
_confirm(inId);
}
/**
* @dev 验证者确认转入操作
* @param inId 转入操作ID
* @notice 验证者可以单独确认已提交的转入操作
*/
function confirmInTransfer(uint256 inId) public onlyValidator {
_confirm(inId);
}
/**
* @dev 验证者拒绝转入操作
* @param inId 转入操作ID
* @notice 验证者发现问题时可以拒绝转入操作
*/
function rejectInTransfer(uint256 inId) public onlyValidator {
bool rejected = inTransfers[inId].rejections[msg.sender];
if (!rejected) {
inTransfers[inId].rejections[msg.sender] = true;
inTransfers[inId].rejectCounter++;
emit TransferInRejection(inId, msg.sender);
}
}
/**
* @dev 内部确认函数
* @param inId 转入操作ID
* @notice 处理验证者的确认操作,达到条件时自动执行转账
*/
function _confirm(uint256 inId) internal {
require(inTransfers[inId].rejectCounter == 0, "already rejected");
bool confirmed = inTransfers[inId].confirmations[msg.sender];
if (!confirmed) {
// 记录确认
inTransfers[inId].confirmations[msg.sender] = true;
inTransfers[inId].confirmCounter++;
emit TransferInConfirmation(inId, msg.sender);
// 检查是否达到执行条件
if (isChecked(inId)) {
_finalizeTransfer(inId);
}
}
}
/**
* @dev 内部转账执行函数
* @param inId 转入操作ID
* @notice 在获得足够确认后执行实际的代币转账
*/
function _finalizeTransfer(uint256 inId) internal {
address token = inTransfers[inId].token;
address receiver = inTransfers[inId].receiver;
uint256 amount = inTransfers[inId].amount;
// 安全检查
require(inTransfers[inId].executed == false, "already executed");
require(inTransfers[inId].rejectCounter == 0, "already rejected");
require(IERC20(token).balanceOf(address(this)) >= amount, "not have enough token");
// 标记为已执行
inTransfers[inId].executed = true;
// 执行代币转账
require(IERC20(token).transfer(receiver, amount), "transfer err");
// 更新转入总量
inTotal[token] = inTotal[token] + amount;
// 触发执行事件
emit TransferInExecution(inId, receiver, token, amount);
}
/**
* @dev 管理员强制执行转账
* @param inId 转入操作ID
* @param receiver 接收者地址
* @param token 代币地址
* @param amount 转账金额
* @notice 只有合约所有者可以调用,用于紧急情况处理
* @notice 此函数会绕过验证者确认机制
*/
function retryTransfer(uint256 inId, address receiver, address token, uint256 amount)
external
onlyOwner
{
require(inTransfers[inId].executed == false, "already executed");
require(IERC20(token).balanceOf(address(this)) >= amount, "not have enough token");
// 更新转入记录
inTransfers[inId].executed = true;
inTransfers[inId].receiver = receiver;
inTransfers[inId].token = token;
inTransfers[inId].amount = amount;
// 执行转账
IERC20(token).transfer(receiver, amount);
inTotal[token] = inTotal[token] + amount;
emit TransferInExecution(inId, receiver, token, amount);
}
/**
* @dev 提取合约中的代币
* @param token 代币合约地址
* @param amount 提取数量
* @param to 接收地址
* @notice 只有合约所有者可以调用,用于紧急情况或费用提取
*/
function emergencyWithdraw(address token, uint256 amount, address to) external onlyOwner {
require(IERC20(token).balanceOf(address(this)) >= amount, "not enough");
IERC20(token).transfer(to, amount);
}
/**
* @dev 检查转入操作是否已通过验证
* @param inId 转入操作ID
* @return 是否已通过验证(无拒绝且确认数足够)
*/
function isChecked(uint256 inId) public view returns (bool) {
return (inTransfers[inId].rejectCounter == 0 && inTransfers[inId].confirmCounter >= validRequired);
}
/**
* @dev 检查验证者是否已确认指定转入操作
* @param inId 转入操作ID
* @param validator 验证者地址
* @return 是否已确认
*/
function isConfirmed(uint256 inId, address validator) public view returns (bool) {
return inTransfers[inId].confirmations[validator];
}
/**
* @dev 检查验证者是否已拒绝指定转入操作
* @param inId 转入操作ID
* @param validator 验证者地址
* @return 是否已拒绝
*/
function isRejected(uint256 inId, address validator) public view returns (bool) {
return inTransfers[inId].rejections[validator];
}
/**
* @dev 获取指定目标链支持的代币列表
* @param toChainID 目标链ID
* @return 支持转出的代币地址数组
*/
function getSupportedTokensOut(uint256 toChainID) external view returns (address[] memory) {
return supportedTokensOut[toChainID];
}
/**
* @dev 检查代币是否支持转出到指定链
* @param token 代币地址
* @param toChainID 目标链ID
* @return 是否支持
*/
function isTokenTransferEnabled(address token, uint256 toChainID) external view returns (bool) {
return outConfiguration[token][toChainID].enabled &&
outConfiguration[token][toChainID].receiveToken != address(0);
}
}
\ No newline at end of file
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract TestToken is ERC20 {
constructor(string memory name, string memory symbol) ERC20(name, symbol) {
_mint(msg.sender, 100000000 * 10 ** decimals());
}
function mint(address to, uint256 amount) public {
_mint(to, amount);
}
function burn(uint256 amount) public {
_burn(msg.sender, amount);
}
}
\ No newline at end of file
require("@nomicfoundation/hardhat-toolbox");
require("@nomicfoundation/hardhat-ethers");
require("dotenv").config()
// This is a sample Hardhat task. To learn how to create your own go to
// https://hardhat.org/guides/create-task.html
task("accounts", "List of accounts", async (taskArgs, hre) => {
const accounts = await hre.ethers.getSigners()
for (const account of accounts) {
// dump account address and balance.
const balance = await hre.ethers.provider.getBalance(account.address);
console.log(`Address: ${account.address}, Balance: ${hre.ethers.formatEther(balance)} ETH`);
}
})
const privateKeys = [
process.env.DEPLOY_PRIVATE_KEY,
process.env.TREASURY_PRIVATE_KEY
];
module.exports = {
solidity: {
evmVersion: "istanbul",
compilers: [
{
version: "0.8.20",
settings: {
optimizer: {
enabled: true,
runs: 200,
},
},
},
{
version: "0.8.10",
settings: {
optimizer: {
enabled: true,
runs: 200,
},
},
},
],
},
etherscan: {
apiKey: {
'achain': 'empty',
'mova': 'empty',
'bsc':'AVZIFYCHUFHPG9FDKNMHEWJ1VAW1H5U66T'
},
customChains: [
{
network: "achain",
chainId: 100,
urls: {
apiURL: "https://scan.cmp20.bitheart.org/api",
browserURL: "https://scan.cmp20.bitheart.org"
}
},
{
network: "mova",
chainId: 10323,
urls: {
apiURL: "https://scan.mars.movachain.com/api",
browserURL: "https://scan.mars.movachain.com"
}
}
]
},
networks: {
hardhat: {
accounts: [
{ privateKey: privateKeys[0], balance: "10000000000000000000000" },
{ privateKey: privateKeys[1], balance: "10000000000000000000000" }
]
},
achain: {
url: "http://15.206.56.79:28545",
accounts: privateKeys
},
mova: {
url: "https://mars.rpc.movachain.com",
accounts: privateKeys
},
bit: {
url: "https://rpc.mova.bitheart.org",
accounts: privateKeys
},
hpb: {
url: "https://hpbnode.com",
accounts: privateKeys
}
}
};
console.log('Happy developing ✨')
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "testcontract",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"private": true,
"devDependencies": {
"@nomicfoundation/hardhat-toolbox": "^5.0.0",
"hardhat": "^2.24.0"
},
"dependencies": {
"@openzeppelin/contracts": "^5.3.0",
"dotenv": "^16.5.0",
"fp-ts": "^2.16.11"
}
}
const fs = require("fs");
const path = require("path");
const hre = require("hardhat");
const DEPLOY_FILE = path.join(__dirname, "deploy.json");
async function deploy() {
const token = await hre.ethers.getContractFactory(
"TestToken"
);
const tokenA = await token.deploy("Test Token Ali", "TTA");
await tokenA.waitForDeployment();
const tokenAAddr = await tokenA.getAddress();
const tokenB = await token.deploy("Test Token Bob", "TTB");
await tokenB.waitForDeployment();
const tokenBAddr = await tokenB.getAddress();
// Deploy the contract
const factory = await hre.ethers.getContractFactory(
"Bridge"
);
[owner] = await hre.ethers.getSigners();
const contract = await factory.deploy(owner.address);
await contract.waitForDeployment();
const bridgeAddr = await contract.getAddress();
const chainId = await hre.ethers.provider.getNetwork().then(network => network.chainId);
await saveDeployment(chainId, tokenAAddr, tokenBAddr, bridgeAddr);
}
function saveDeployment(chainId, tokenA, tokenB, bridge) {
let deployData = {};
if (fs.existsSync(DEPLOY_FILE)) {
deployData = JSON.parse(fs.readFileSync(DEPLOY_FILE, "utf8"));
}
deployData[chainId] = {
tokenA,
tokenB,
bridge,
};
fs.writeFileSync(DEPLOY_FILE, JSON.stringify(deployData, null, 2));
console.log("Deployment at chain ", chainId, " saved to deploy.json");
}
// Define the script
async function main() {
await deploy();
}
// Run the script
main().catch((error) => {
console.error("Error:", error);
});
\ No newline at end of file
{
"269": {
"tokenA": "0xF5c10392D841d55C41EBf696A4E437b2DC91f5D3",
"tokenB": "0x6A24C27cF83dAFf6227fa03852465aA65cF22527",
"bridge": "0x9a06d0CfAFc19a4bfe0ecd5f8bC20A26a88fA227"
},
"10323": {
"tokenA": "0xF5c10392D841d55C41EBf696A4E437b2DC91f5D3",
"tokenB": "0x6A24C27cF83dAFf6227fa03852465aA65cF22527",
"bridge": "0x9a06d0CfAFc19a4bfe0ecd5f8bC20A26a88fA227"
}
}
\ No newline at end of file
const fs = require("fs");
const path = require("path");
const hre = require("hardhat");
let INITIAL_VALIDATORS = process.env.BRIDGE_VALIDATORS
let TREASURY = process.env.TREASURY
const DEPLOY_FILE = path.join(__dirname, "deploy.json");
async function addValidators(contract, validators) {
let validatorList = validators.split(',')
for (let i = 0; i < validatorList.length; i++) {
var tx = await contract.addValidator(validatorList[i])
await tx.wait();
console.log("Added validator:", validatorList[i]);
}
}
async function setTreasury(contract, treasury) {
var tx = await contract.setTreasury(treasury);
await tx.wait();
console.log("Set treasury to:", treasury);
}
async function mintToken(tokenA, tokenB, user) {
var amount = hre.ethers.parseEther("1000000")
var tx = await tokenA.mint(user, amount);
await tx.wait();
console.log("Minted TTA to user:", user, " amount:", hre.ethers.formatEther(amount));
tx = await tokenB.mint(user, amount);
await tx.wait();
console.log("Minted TTB to user:", user, " amount:", hre.ethers.formatEther(amount));
}
async function treasuryApprove(tokenA, tokenB, bridgeAddr, treasury) {
var tx = await tokenA.connect(treasury).approve(bridgeAddr, hre.ethers.MaxUint256);
await tx.wait();
console.log("Approved TTA for bridge:", bridgeAddr);
tx = await tokenB.connect(treasury).approve(bridgeAddr, hre.ethers.MaxUint256);
await tx.wait();
console.log("Approved TTB for bridge:", bridgeAddr);
}
async function setConfig(tokenA, tokenB, bridge) {
if (!INITIAL_VALIDATORS || !TREASURY) {
throw new Error("Environment variables BRIDGE_VALIDATORS or TREASURY are not set.");
}
[owner, treasury] = await hre.ethers.getSigners();
await addValidators(bridge, INITIAL_VALIDATORS);
await setTreasury(bridge, TREASURY);
// if treasury is exist, do approve .
var bridgeAddr = await bridge.getAddress();
if (treasury.address) {
// test environment, mint token and approve.
await mintToken(tokenA, tokenB, TREASURY);
await treasuryApprove(tokenA, tokenB, bridgeAddr, treasury);
}
}
async function getDeploy(chainId) {
if (!fs.existsSync(DEPLOY_FILE)) {
throw new Error("deploy.json file not found");
}
const deployData = JSON.parse(fs.readFileSync(DEPLOY_FILE, "utf8"));
if (!deployData[chainId]) {
throw new Error(`No deployment data found for chainId ${chainId}`);
}
const addrs = deployData[chainId];
const tokenA = await hre.ethers.getContractAt("TestToken", addrs.tokenA);
const tokenB = await hre.ethers.getContractAt("TestToken", addrs.tokenB);
const bridge = await hre.ethers.getContractAt("Bridge", addrs.bridge);
return {tokenA, tokenB, bridge}
}
async function setOutConfig(curTokenMap, targetTokenMap, targetChainId, bridge) {
const outConfigA = {
receiveToken: await targetTokenMap.tokenA.getAddress(),
fee: hre.ethers.parseEther("0.05"),
limit: hre.ethers.parseEther("1000"),
isBurn: false,
enabled: true,
};
const outConfigB = {
receiveToken: await targetTokenMap.tokenB.getAddress(),
fee: hre.ethers.parseEther("0.04"),
limit: hre.ethers.parseEther("1000"),
isBurn: false,
enabled: true,
}
var tx = await bridge.changeOutConfig(
await curTokenMap.tokenA.getAddress(),
targetChainId, // Target chain ID
outConfigA
);
await tx.wait();
console.log("Set out config for tokenA to chain", targetChainId, "tx", tx.hash);
tx = await bridge.changeOutConfig(
await curTokenMap.tokenB.getAddress(),
targetChainId, // Target chain ID
outConfigB
);
await tx.wait();
console.log("Set out config for tokenB to chain", targetChainId, "tx", tx.hash);
}
// Define the script
async function main() {
const chainId = await hre.ethers.provider.getNetwork().then(network => network.chainId);
const targetChainId = chainId === 269n ? 10323 : 269;
console.log("Current chain ID:", chainId, "Target chain ID:", targetChainId);
const curChainDeploy = await getDeploy(chainId);
const targetChainDeploy = await getDeploy(targetChainId);
await setConfig(curChainDeploy.tokenA, curChainDeploy.tokenB, curChainDeploy.bridge);
await setOutConfig(curChainDeploy, targetChainDeploy, targetChainId, curChainDeploy.bridge);
console.log("Configuration set successfully for chain", chainId);
}
// Run the script
main().catch((error) => {
console.error("Error:", error);
});
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment