Commit 767dbf36 authored by Developer's avatar Developer

update

parent 4c71963d
File added
#DEPLOY_PRIVATE_KEY=0x59a4fbbeb5df606612f8c5ad298483646a42b232b1184b7f54607e1a5ea13284
# mova network
#DEPLOY_PRIVATE_KEY=0xca8bdd675179e7411b74518fe8a5f0691ff1edcc435a5d92402e2efe5dcd3a0c
DEPLOY_PRIVATE_KEY=0x2c131e5855e9005a1b42837a54f0033191cb53eb243217e95b360b4c4a2bf4f9
# mova testnet genesis account
#DEPLOY_PRIVATE_KEY=0x8a35953f17f8109020c09a95963aea73b34e5f02d31451ce91fd4d8380476730
# apolo 0x119BA92c15337FfB37871e44F3f9102409D95F12
#DEPLOY_PRIVATE_KEY=0x0a5dc451f7c8ce153e0aa1cccfd9c0dc21f4bb67d15f5f92e9162e4df99dd92d
# mova bitheart.org
#DEPLOY_PRIVATE_KEY=0x7d1d4ad0827d007aea8001ef205549bb054e90876337f6f1667b93a952d1f34c
#DEPLOY_PRIVATE_KEY=0x2aeab02a323f49acb0346852958a76fb6d94539ad322151b8472329adea5db4d
#DEPLOY_PRIVATE_KEY=0x3b62ea39e3751c489fa1965fee80cff1546a9179821c3d34d9b3f3fbfafb3924
NAME_SIGNER_KEY=0x3b62ea39e3751c489fa1965fee80cff1546a9179821c3d34d9b3f3fbfafb3924
# bridge envrionment
TREASURY_ADDRESS=""
BRIDGE_VALIDATORS="0x00008B378851EBa80F66575B2fBC34e9e0b7BAa7,0x014702f872C1E0A7314a8D25aC1034257F28BBf5"
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.26;
interface ITTest {
function test() external;
}
contract Attacker {
ITTest ttest;
uint public count;
constructor(address _ttest) {
ttest = ITTest(_ttest);
}
function attack() public {
ttest.test();
}
fallback() external payable {
// This is the re-entrant call
if (address(ttest) != address(0)) {
ttest.test();
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
/// @notice ComplexToken - a feature-rich token used for stress-testing nodes.
/// Features:
/// - ERC20 + EIP-2612 permit
/// - Pausable (owner)
/// - Snapshot (owner)
/// - Batch transfer
/// - Transfer fee with optional fee recipient
/// - Simple staking: deposit/withdraw with rewards accrual
/// - reentrancy protection on stateful flows
contract ComplexToken is ERC20, ERC20Permit, Ownable {
// fee in basis points (parts per 10,000)
uint16 public feeBps;
address public feeRecipient;
// staking
mapping(address => uint256) private _stakes;
mapping(address => uint256) private _stakeRewards; // accumulated rewards in token units
uint256 public totalStaked;
uint256 public rewardRatePerSecond = 1e15; // 0.001 token per second per staked token unit scaled (simple)
event BatchTransfer(address indexed sender, uint256 count);
event FeeChanged(uint16 newFeeBps, address newRecipient);
event Staked(address indexed user, uint256 amount);
event Unstaked(address indexed user, uint256 amount);
event RewardsClaimed(address indexed user, uint256 amount);
constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_) ERC20Permit(name_) Ownable(msg.sender) {
feeBps = 0;
feeRecipient = owner();
// mint some initial supply to owner for testing convenience
_mint(msg.sender, 1_000_000 * (10 ** decimals()));
}
function setFee(uint16 _feeBps, address _recipient) external onlyOwner {
require(_feeBps <= 1000, "fee too high"); // max 10%
feeBps = _feeBps;
feeRecipient = _recipient;
emit FeeChanged(_feeBps, _recipient);
}
// -------------------- Transfer logic with fee --------------------
// function _transfer(address sender, address recipient, uint256 amount) internal virtual override {
// if (feeBps == 0 || sender == feeRecipient || recipient == feeRecipient) {
// super._transfer(sender, recipient, amount);
// return;
// }
//
// uint256 fee = (amount * feeBps) / 10000;
// uint256 amountAfter = amount - fee;
// if (fee > 0) {
// super._transfer(sender, feeRecipient, fee);
// }
// super._transfer(sender, recipient, amountAfter);
// }
// -------------------- Batch transfer --------------------
/// @notice transfer to multiple recipients in one call
/// @param recipients array of recipient addresses
/// @param amounts array of amounts matching recipients
function batchTransfer(address[] calldata recipients, uint256[] calldata amounts) public {
require(recipients.length == amounts.length, "len mismatch");
uint256 len = recipients.length;
for (uint256 i = 0; i < len; ++i) {
_transfer(msg.sender, recipients[i], amounts[i]);
}
emit BatchTransfer(msg.sender, len);
}
// -------------------- Simple staking --------------------
/// @notice deposit tokens to stake
function stake(uint256 amount) external {
require(amount > 0, "zero");
_updateRewards(msg.sender);
_transfer(msg.sender, address(this), amount);
_stakes[msg.sender] += amount;
totalStaked += amount;
emit Staked(msg.sender, amount);
}
/// @notice withdraw staked tokens (claims rewards as well)
function unstake(uint256 amount) external {
require(amount > 0, "zero");
uint256 staked = _stakes[msg.sender];
require(staked >= amount, "insufficient stake");
_updateRewards(msg.sender);
_stakes[msg.sender] = staked - amount;
totalStaked -= amount;
_transfer(address(this), msg.sender, amount);
emit Unstaked(msg.sender, amount);
}
/// @notice claim staking rewards
function claimRewards() external {
_updateRewards(msg.sender);
uint256 reward = _stakeRewards[msg.sender];
require(reward > 0, "no rewards");
_stakeRewards[msg.sender] = 0;
_transfer(address(this), msg.sender, reward);
emit RewardsClaimed(msg.sender, reward);
}
function stakeOf(address account) external view returns (uint256) {
return _stakes[account];
}
function pendingRewards(address account) external view returns (uint256) {
// approximate using stored rewards + accrued since last update
uint256 base = _stakeRewards[account];
uint256 staked = _stakes[account];
if (staked == 0) return base;
// simplistic accrual: rewardRatePerSecond * staked * elapsed / 1e18
// we don't store lastUpdate timestamps per account here to keep contract simpler for stress testing
return base; // accurate only after _updateRewards
}
// internal helper: naive reward accrual per call
function _updateRewards(address account) internal {
// For stress testing we do a cheap deterministic increment based on block.timestamp
// This is intentionally naive: on each interaction we add a small fixed reward proportional to stake.
uint256 staked = _stakes[account];
if (staked == 0) return;
// reward = staked * rewardRatePerSecond (scaled) / 1e18
// to avoid tiny fractions and complexity, compute as (staked * rewardRatePerSecond) / 1e18
uint256 reward = (staked * rewardRatePerSecond) / 1e18;
if (reward > 0) {
// mint rewards to contract balance if needed
_mint(address(this), reward);
_stakeRewards[account] += reward;
}
}
// -------------------- Utility heavy function (complex state changes) --------------------
/// @notice heavyOperation performs multiple transfers, burns, and mints in a single call to exercise node state changes
function heavyOperation(address[] calldata targets, uint256[] calldata amounts) external {
require(targets.length == amounts.length, "len mismatch");
uint256 len = targets.length;
for (uint256 i = 0; i < len; ++i) {
address to = targets[i];
uint256 amt = amounts[i];
if (amt == 0) continue;
// transfer a portion to target
uint256 part = amt / 2;
_transfer(msg.sender, to, part);
// burn a small part
uint256 burnAmt = amt / 10;
if (burnAmt > 0) _burn(msg.sender, burnAmt);
// mint a tiny reward to target
uint256 mintAmt = amt / 100;
if (mintAmt > 0) _mint(to, mintAmt);
}
}
// -------------------- Allow contract to receive ETH (for advanced tests) --------------------
receive() external payable {}
// Fallback to accept arbitrary calls
fallback() external payable {}
}
// filepath: /Users/luxq/work/wuban/testcontract/contracts/Distributor.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.12;
/// @title Distributor - generate 700 pseudo-random addresses and distribute ETH to them
contract Distributor {
address[] public recipients;
uint256 public constant RECIPIENT_COUNT = 700;
/// @notice Construct the contract and generate RECIPIENT_COUNT addresses from a seed
/// @param seed A user provided seed to influence address generation (can be 0)
constructor(bytes32 seed) {
// generate deterministic pseudo-random addresses using keccak256 of seed, block timestamp and index
// Note: these are not real externally-owned accounts with known private keys — they are just 20-byte addresses
// and may be inaccessible if nobody controls the corresponding private key. Use for testing/stress only.
recipients = new address[](RECIPIENT_COUNT);
for (uint256 i = 0; i < RECIPIENT_COUNT; ++i) {
// mix seed with block.timestamp and sender to get some variability
bytes32 h = keccak256(abi.encodePacked(seed, block.timestamp, msg.sender, i));
// take the lower 160 bits as an address
recipients[i] = address(uint160(uint256(h)));
}
}
/// @notice Get the number of recipients
function recipientsCount() external pure returns (uint256) {
return RECIPIENT_COUNT;
}
/// @notice Distribute msg.value equally to all recipients.
/// @dev This will attempt RECIPIENT_COUNT transfers in a single transaction and may run out of gas.
/// It's provided for convenience, but prefer `distributeRange` with smaller ranges for production use.
function distributeAll() external payable {
uint256 total = msg.value;
require(total > 0, "No ETH provided");
uint256 per = total / RECIPIENT_COUNT;
require(per > 0, "Amount too small for equal distribution");
for (uint256 i = 0; i < RECIPIENT_COUNT; ++i) {
// use call to forward gas and avoid 2300 stipend issues
(bool success, ) = recipients[i].call{value: per}("");
// do not revert on single failure to maximize distribution; failed transfers keep funds in contract
if (!success) {
// continue; failed amount stays in contract
}
}
// refund any leftover (due to integer division or failed sends) back to sender
uint256 leftover = address(this).balance;
if (leftover > 0) {
payable(msg.sender).transfer(leftover);
}
}
/// @notice Distribute `amountPer` wei to each recipient in the half-open range [start, end)
/// @param start The starting index (inclusive)
/// @param end The end index (exclusive)
/// @param amountPer Amount in wei to send to each recipient in the range
function distributeRange(uint256 start, uint256 end, uint256 amountPer) external payable {
require(start < end && end <= RECIPIENT_COUNT, "Invalid range");
uint256 count = end - start;
uint256 required = count * amountPer;
require(msg.value >= required, "Insufficient ETH provided");
for (uint256 i = start; i < end; ++i) {
(bool success, ) = recipients[i].call{value: amountPer}("");
if (!success) {
// on failure, don't revert — leave failed amount in contract
}
}
// refund any extra wei back to sender
uint256 leftover = address(this).balance;
if (leftover > 0) {
payable(msg.sender).transfer(leftover);
}
}
/// @notice Return a copy of recipients (careful: large array)
function getRecipients() external view returns (address[] memory) {
return recipients;
}
// Allow the contract to receive ETH
receive() external payable {}
}
// SPDX-License-Identifier: MIT
// Created by Caduceus.foundation
pragma solidity ^0.8.20;
interface IERC20Token {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address from, address to, uint256 amount) external returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20Token {
using Address for address;
function safeTransfer(
IERC20Token token,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
function safeTransferFrom(
IERC20Token token,
address from,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
/**
* @dev Deprecated. This function has issues similar to the ones found in
* {IERC20-approve}, and its usage is discouraged.
*
* Whenever possible, use {safeIncreaseAllowance} and
* {safeDecreaseAllowance} instead.
*/
function safeApprove(
IERC20Token token,
address spender,
uint256 value
) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
require(
(value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
function safeIncreaseAllowance(
IERC20Token token,
address spender,
uint256 value
) internal {
uint256 newAllowance = token.allowance(address(this), spender) + value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
function safeDecreaseAllowance(
IERC20Token token,
address spender,
uint256 value
) internal {
unchecked {
uint256 oldAllowance = token.allowance(address(this), spender);
require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
uint256 newAllowance = oldAllowance - value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20Token token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
if (returndata.length > 0) {
// Return data is optional
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCall(target, data, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value
) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
require(isContract(target), "Address: call to non-contract");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
require(isContract(target), "Address: static call to non-contract");
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
require(isContract(target), "Address: delegate call to non-contract");
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
contract HandoutContract {
using SafeERC20Token for IERC20Token;
address private _owner;
mapping(address=>bool) _admins;
modifier onlyOwner() {
require(msg.sender == _owner, "Only owner can do it");
_;
}
modifier onlyAdmin() {
require(_admins[msg.sender], "Only admin can do it");
_;
}
event Transfer(address indexed src, address indexed dst, uint value);
constructor() {
_owner = msg.sender;
_admins[msg.sender] = true;
}
receive() external payable {
}
function getAdmin(address addr) public view returns (bool) {
return _admins[addr];
}
function setAdmin(address[] memory lst, bool b) public onlyOwner() {
for (uint i = 0; i < lst.length; i++) {
_admins[lst[i]] = b;
}
}
function Handout(address[] memory to, uint v) public onlyAdmin {
uint value = v * 10 ** 18;
for (uint i = 0; i < to.length; i++) {
require(address(this).balance >= value);
payable(to[i]).transfer(value);
emit Transfer(address(this), to[i], value);
}
}
function Handout(address[] memory to, uint v, uint d) public onlyAdmin {
require(d <= 18, "Decimals must be less than 18");
uint value = v * 10 ** (18 - d);
for (uint i = 0; i < to.length; i++) {
require(address(this).balance >= value);
payable(to[i]).transfer(value);
emit Transfer(address(this), to[i], value);
}
}
function Handout(address[] memory to, uint[] memory v) public onlyAdmin {
for (uint i = 0; i < to.length; i++) {
uint value = v[i] * 10 ** 18;
require(address(this).balance >= value);
payable(to[i]).transfer(value);
emit Transfer(address(this), to[i], value);
}
}
function Handout(address[] memory to, uint[] memory v, uint d) public onlyAdmin {
require(d <= 18, "Decimals must be less than 18");
for (uint i = 0; i < to.length; i++) {
uint value = v[i] * 10 ** (18 - d);
require(address(this).balance >= value);
payable(to[i]).transfer(value);
emit Transfer(address(this), to[i], value);
}
}
function Handout(address token, address[] memory to, uint v) public onlyAdmin {
uint value = v * 10 ** 18;
for (uint i = 0; i < to.length; i++) {
require(IERC20Token(token).balanceOf(address(this)) >= value);
IERC20Token(token).safeTransfer(to[i], value);
}
}
function Handout(address token, address[] memory to, uint v, uint d) public onlyAdmin {
require(d <= 18, "Decimals must be less than 18");
uint value = v * 10 ** (18 - d);
for (uint i = 0; i < to.length; i++) {
require(IERC20Token(token).balanceOf(address(this)) >= value);
IERC20Token(token).safeTransfer(to[i], value);
}
}
function Handout(address token, address[] memory to, uint[] memory v) public onlyAdmin {
for (uint i = 0; i < to.length; i++) {
uint value = v[i] * 10 ** 18;
require(IERC20Token(token).balanceOf(address(this)) >= value);
IERC20Token(token).safeTransfer(to[i], value);
}
}
function Handout(address token, address[] memory to, uint[] memory v, uint d) public onlyOwner {
for (uint i = 0; i < to.length; i++) {
uint value = v[i] * 10 ** (18 - d);
require(IERC20Token(token).balanceOf(address(this)) >= value);
IERC20Token(token).safeTransfer(to[i], value);
}
}
}
\ No newline at end of file
// SPDX-License-Identifier: MIT
pragma solidity 0.8.12;
/// @title Multicall3
/// @notice Aggregate results from multiple function calls
/// @dev Multicall & Multicall2 backwards-compatible
/// @dev Aggregate methods are marked `payable` to save 24 gas per call
/// @author Michael Elliot <mike@makerdao.com>
/// @author Joshua Levine <joshua@makerdao.com>
/// @author Nick Johnson <arachnid@notdot.net>
/// @author Andreas Bigger <andreas@nascent.xyz>
/// @author Matt Solomon <matt@mattsolomon.dev>
contract Multicall3 {
struct Call {
address target;
bytes callData;
}
struct Call3 {
address target;
bool allowFailure;
bytes callData;
}
struct Call3Value {
address target;
bool allowFailure;
uint256 value;
bytes callData;
}
struct Result {
bool success;
bytes returnData;
}
/// @notice Backwards-compatible call aggregation with Multicall
/// @param calls An array of Call structs
/// @return blockNumber The block number where the calls were executed
/// @return returnData An array of bytes containing the responses
function aggregate(Call[] calldata calls) public payable returns (uint256 blockNumber, bytes[] memory returnData) {
blockNumber = block.number;
uint256 length = calls.length;
returnData = new bytes[](length);
Call calldata call;
for (uint256 i = 0; i < length;) {
bool success;
call = calls[i];
(success, returnData[i]) = call.target.call(call.callData);
require(success, "Multicall3: call failed");
unchecked { ++i; }
}
}
/// @notice Backwards-compatible with Multicall2
/// @notice Aggregate calls without requiring success
/// @param requireSuccess If true, require all calls to succeed
/// @param calls An array of Call structs
/// @return returnData An array of Result structs
function tryAggregate(bool requireSuccess, Call[] calldata calls) public payable returns (Result[] memory returnData) {
uint256 length = calls.length;
returnData = new Result[](length);
Call calldata call;
for (uint256 i = 0; i < length;) {
Result memory result = returnData[i];
call = calls[i];
(result.success, result.returnData) = call.target.call(call.callData);
if (requireSuccess) require(result.success, "Multicall3: call failed");
unchecked { ++i; }
}
}
/// @notice Backwards-compatible with Multicall2
/// @notice Aggregate calls and allow failures using tryAggregate
/// @param calls An array of Call structs
/// @return blockNumber The block number where the calls were executed
/// @return blockHash The hash of the block where the calls were executed
/// @return returnData An array of Result structs
function tryBlockAndAggregate(bool requireSuccess, Call[] calldata calls) public payable returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData) {
blockNumber = block.number;
blockHash = blockhash(block.number);
returnData = tryAggregate(requireSuccess, calls);
}
/// @notice Backwards-compatible with Multicall2
/// @notice Aggregate calls and allow failures using tryAggregate
/// @param calls An array of Call structs
/// @return blockNumber The block number where the calls were executed
/// @return blockHash The hash of the block where the calls were executed
/// @return returnData An array of Result structs
function blockAndAggregate(Call[] calldata calls) public payable returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData) {
(blockNumber, blockHash, returnData) = tryBlockAndAggregate(true, calls);
}
/// @notice Aggregate calls, ensuring each returns success if required
/// @param calls An array of Call3 structs
/// @return returnData An array of Result structs
function aggregate3(Call3[] calldata calls) public payable returns (Result[] memory returnData) {
uint256 length = calls.length;
returnData = new Result[](length);
Call3 calldata calli;
for (uint256 i = 0; i < length;) {
Result memory result = returnData[i];
calli = calls[i];
(result.success, result.returnData) = calli.target.call(calli.callData);
assembly {
// Revert if the call fails and failure is not allowed
// `allowFailure := calldataload(add(calli, 0x20))` and `success := mload(result)`
if iszero(or(calldataload(add(calli, 0x20)), mload(result))) {
// set "Error(string)" signature: bytes32(bytes4(keccak256("Error(string)")))
mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000)
// set data offset
mstore(0x04, 0x0000000000000000000000000000000000000000000000000000000000000020)
// set length of revert string
mstore(0x24, 0x0000000000000000000000000000000000000000000000000000000000000017)
// set revert string: bytes32(abi.encodePacked("Multicall3: call failed"))
mstore(0x44, 0x4d756c746963616c6c333a2063616c6c206661696c6564000000000000000000)
revert(0x00, 0x64)
}
}
unchecked { ++i; }
}
}
/// @notice Aggregate calls with a msg value
/// @notice Reverts if msg.value is less than the sum of the call values
/// @param calls An array of Call3Value structs
/// @return returnData An array of Result structs
function aggregate3Value(Call3Value[] calldata calls) public payable returns (Result[] memory returnData) {
uint256 valAccumulator;
uint256 length = calls.length;
returnData = new Result[](length);
Call3Value calldata calli;
for (uint256 i = 0; i < length;) {
Result memory result = returnData[i];
calli = calls[i];
uint256 val = calli.value;
// Humanity will be a Type V Kardashev Civilization before this overflows - andreas
// ~ 10^25 Wei in existence << ~ 10^76 size uint fits in a uint256
unchecked { valAccumulator += val; }
(result.success, result.returnData) = calli.target.call{value: val}(calli.callData);
assembly {
// Revert if the call fails and failure is not allowed
// `allowFailure := calldataload(add(calli, 0x20))` and `success := mload(result)`
if iszero(or(calldataload(add(calli, 0x20)), mload(result))) {
// set "Error(string)" signature: bytes32(bytes4(keccak256("Error(string)")))
mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000)
// set data offset
mstore(0x04, 0x0000000000000000000000000000000000000000000000000000000000000020)
// set length of revert string
mstore(0x24, 0x0000000000000000000000000000000000000000000000000000000000000017)
// set revert string: bytes32(abi.encodePacked("Multicall3: call failed"))
mstore(0x44, 0x4d756c746963616c6c333a2063616c6c206661696c6564000000000000000000)
revert(0x00, 0x84)
}
}
unchecked { ++i; }
}
// Finally, make sure the msg.value = SUM(call[0...i].value)
require(msg.value == valAccumulator, "Multicall3: value mismatch");
}
/// @notice Returns the block hash for the given block number
/// @param blockNumber The block number
function getBlockHash(uint256 blockNumber) public view returns (bytes32 blockHash) {
blockHash = blockhash(blockNumber);
}
/// @notice Returns the block number
function getBlockNumber() public view returns (uint256 blockNumber) {
blockNumber = block.number;
}
/// @notice Returns the block coinbase
function getCurrentBlockCoinbase() public view returns (address coinbase) {
coinbase = block.coinbase;
}
/// @notice Returns the block difficulty
function getCurrentBlockDifficulty() public view returns (uint256 difficulty) {
difficulty = block.difficulty;
}
/// @notice Returns the block gas limit
function getCurrentBlockGasLimit() public view returns (uint256 gaslimit) {
gaslimit = block.gaslimit;
}
/// @notice Returns the block timestamp
function getCurrentBlockTimestamp() public view returns (uint256 timestamp) {
timestamp = block.timestamp;
}
/// @notice Returns the (ETH) balance of a given address
function getEthBalance(address addr) public view returns (uint256 balance) {
balance = addr.balance;
}
/// @notice Returns the block hash of the last block
function getLastBlockHash() public view returns (bytes32 blockHash) {
unchecked {
blockHash = blockhash(block.number - 1);
}
}
/// @notice Gets the base fee of the given block
/// @notice Can revert if the BASEFEE opcode is not implemented by the given chain
function getBasefee() public view returns (uint256 basefee) {
basefee = block.basefee;
}
/// @notice Returns the chain id
function getChainId() public view returns (uint256 chainid) {
chainid = block.chainid;
}
}
\ No newline at end of file
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.20;
contract WMDD {
string public name = "Wrapped MMDD";
string public symbol = "MMDD";
uint8 public decimals = 18;
event Approval(address indexed src, address indexed guy, uint wad);
event Transfer(address indexed src, address indexed dst, uint wad);
event Deposit(address indexed dst, uint wad);
event Withdrawal(address indexed src, uint wad);
mapping(address => uint) public balanceOf;
mapping(address => mapping(address => uint)) public allowance;
// 接收以太币
receive() external payable {
deposit();
}
function deposit() public payable {
balanceOf[msg.sender] += msg.value;
emit Deposit(msg.sender, msg.value);
}
function withdraw(uint wad) public {
require(balanceOf[msg.sender] >= wad, "Insufficient balance");
balanceOf[msg.sender] -= wad;
// 使用 call 发送以太币
(bool success, ) = msg.sender.call{value: wad}("");
require(success, "Transfer failed");
emit Withdrawal(msg.sender, wad);
}
function totalSupply() public view returns (uint) {
return address(this).balance;
}
function approve(address guy, uint wad) public returns (bool) {
allowance[msg.sender][guy] = wad;
emit Approval(msg.sender, guy, wad);
return true;
}
function transfer(address dst, uint wad) public returns (bool) {
return transferFrom(msg.sender, dst, wad);
}
function transferFrom(address src, address dst, uint wad) public returns (bool) {
require(balanceOf[src] >= wad, "Insufficient balance");
if (src != msg.sender && allowance[src][msg.sender] != type(uint).max) {
require(allowance[src][msg.sender] >= wad, "Allowance exceeded");
allowance[src][msg.sender] -= wad;
}
balanceOf[src] -= wad;
balanceOf[dst] += wad;
emit Transfer(src, dst, wad);
return true;
}
}
\ No newline at end of file
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.12;
contract BlockInfo {
uint256 blockheight = 0;
uint256 blocktime = 0;
function setBlockInfo() public {
blockheight = block.number;
blocktime = block.timestamp;
}
function getBlockInfo() public view returns (uint256, uint256) {
return (blockheight, blocktime);
}
}
\ No newline at end of file
// 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.24;
contract TestMCOPY {
function testExplicitMCopy() public pure returns (bytes32 result) {
assembly {
mstore(0x00, 0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF)
// mcopy(dstOffset, srcOffset, length)
mcopy(0x80, 0x00, 0x20)
result := mload(0x80)
}
}
}
\ No newline at end of file
pragma solidity >=0.5.0;
pragma experimental ABIEncoderV2;
/// @title Multicall2 - Aggregate results from multiple read-only function calls
/// @author Michael Elliot <mike@makerdao.com>
/// @author Joshua Levine <joshua@makerdao.com>
/// @author Nick Johnson <arachnid@notdot.net>
contract Multicall2 {
struct Call {
address target;
bytes callData;
}
struct Result {
bool success;
bytes returnData;
}
function aggregate(Call[] memory calls) public returns (uint256 blockNumber, bytes[] memory returnData) {
blockNumber = block.number;
returnData = new bytes[](calls.length);
for(uint256 i = 0; i < calls.length; i++) {
(bool success, bytes memory ret) = calls[i].target.call(calls[i].callData);
require(success, "Multicall aggregate: call failed");
returnData[i] = ret;
}
}
function blockAndAggregate(Call[] memory calls) public returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData) {
(blockNumber, blockHash, returnData) = tryBlockAndAggregate(true, calls);
}
function getBlockHash(uint256 blockNumber) public view returns (bytes32 blockHash) {
blockHash = blockhash(blockNumber);
}
function getBlockNumber() public view returns (uint256 blockNumber) {
blockNumber = block.number;
}
function getCurrentBlockCoinbase() public view returns (address coinbase) {
coinbase = block.coinbase;
}
function getCurrentBlockDifficulty() public view returns (uint256 difficulty) {
difficulty = block.difficulty;
}
function getCurrentBlockGasLimit() public view returns (uint256 gaslimit) {
gaslimit = block.gaslimit;
}
function getCurrentBlockTimestamp() public view returns (uint256 timestamp) {
timestamp = block.timestamp;
}
function getEthBalance(address addr) public view returns (uint256 balance) {
balance = addr.balance;
}
function getLastBlockHash() public view returns (bytes32 blockHash) {
blockHash = blockhash(block.number - 1);
}
function tryAggregate(bool requireSuccess, Call[] memory calls) public returns (Result[] memory returnData) {
returnData = new Result[](calls.length);
for(uint256 i = 0; i < calls.length; i++) {
(bool success, bytes memory ret) = calls[i].target.call(calls[i].callData);
if (requireSuccess) {
require(success, "Multicall2 aggregate: call failed");
}
returnData[i] = Result(success, ret);
}
}
function tryBlockAndAggregate(bool requireSuccess, Call[] memory calls) public returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData) {
blockNumber = block.number;
blockHash = blockhash(block.number);
returnData = tryAggregate(requireSuccess, calls);
}
}
\ No newline at end of file
// Compatible with OpenZeppelin Contracts ^5.0.0
pragma solidity ^0.8.26;
import "@openzeppelin/contracts/utils/ReentrancyGuardTransient.sol";
contract TTest is ReentrancyGuardTransient{
function testBASEFEE() public view returns(uint256) {
return block.basefee;
}
function testPREVRANDAO() public view returns(uint256) {
return block.prevrandao;
}
function testBLOBHASH() public view returns (bytes32) {
return blobhash(1);
}
function testBLOBFEE() public view returns(uint256) {
return block.blobbasefee;
}
event Call(uint256);
function test() nonReentrant public {
emit Call(1);
(bool success, ) = address(msg.sender).call{gas: 250000}("");
require(success, "rvt");
}
}
\ No newline at end of file
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;
contract RevertContract {
event log(string message);
modifier minimumEther(uint256 _amount) {
require(_amount >= 1*10**18, "minimum 1 ether");
_;
}
function sendViaTransfer(address payable _to, uint256 _amount) public payable minimumEther(_amount) {
require(_amount >= 1*10**18, "minimum 1 ether");
_to.transfer(_amount);
}
function sendViaSend(address payable _to, uint256 _amount) public payable minimumEther(_amount) returns (bool) {
emit log("sendViaSend");
bool sent = _to.send(_amount);
require(sent, "Send failed");
return sent;
}
function sendViaCall(address payable _to, uint256 _amount) public payable minimumEther(_amount) returns (bool) {
emit log("sendViaCall");
(bool success, ) = _to.call{value: _amount}("");
require(success, "Call failed");
return success;
}
receive() external payable {}
}
\ 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, 1000000000 * 10 ** decimals());
}
function burn(uint256 amount) public {
_burn(msg.sender, amount);
}
}
\ No newline at end of file
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
/**
* @title Bridge
* @dev 跨链桥合约,支持代币在不同区块链之间转移
* @notice 使用多签验证者机制确保跨链转移的安全性
*/
contract TestAbi 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) {
}
// 获取当前链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;
}
function getSubmitData(submitParams memory params)
external
view
returns (bytes32) {
return keccak256(abi.encode(params.outId, params.fromChainID, params.sender, params.sendToken, params.sendAmount, params.toChainID, params.receiver, params.token));
}
/**
* @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;
pragma solidity ^0.8.12;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
......
......@@ -33,27 +33,7 @@ const accounts = { mnemonic }
/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: {
evmVersion: "istanbul",
compilers: [
{
version: "0.8.26",
settings: {
evmVersion: "cancun",
optimizer: {
enabled: true,
runs: 200,
},
},
},
{
version: "0.8.24",
settings: {
optimizer: {
enabled: true,
runs: 200,
},
},
},
{
version: "0.8.20",
settings: {
......@@ -64,7 +44,7 @@ module.exports = {
},
},
{
version: "0.8.10",
version: "0.8.12",
settings: {
optimizer: {
enabled: true,
......@@ -122,10 +102,15 @@ module.exports = {
},
networks: {
hardhat: {
hardfork: "cancun",
accounts: [{ privateKey, balance: "10000000000000000000000" }], // Example balance
gas: 10000000,
gasPrice: 10000000000
},
bsc: {
url: "https://bsc-dataseed.bnbchain.org",
accounts: [privateKey]
},
hole: {
url: "https://rpc.hole.bitheart.org",
accounts: [privateKey]
......@@ -134,8 +119,64 @@ module.exports = {
url: "https://rpc.mova.bitheart.org",
accounts: [privateKey]
},
usd1: {
url: "https://rpc.usd1swap.ai",
accounts: [privateKey]
},
mova: {
url: "https://mars.rpc.movachain.com",
url: "https://rpc2.movachain.com",
accounts: [privateKey]
},
movamainnet: {
url: "https://rpc.movachain.com",
accounts: [privateKey]
},
remote: {
url: "http://13.212.120.130:8545",
accounts: [privateKey]
},
a: {
url:"https://1.r.movax.ai",
accounts: [privateKey]
},
b: {
url:"https://2.r.movax.ai",
accounts: [privateKey]
},
c: {
url:"https://3.r.movax.ai",
accounts: [privateKey]
},
d: {
url:"https://4.r.movax.ai",
accounts: [privateKey]
},
e: {
url:"https://5.r.movax.ai",
accounts: [privateKey]
},
n: {
url: "https://a.r.movax.ai",
accounts: [privateKey]
},
m7: {
url: "https://7.r.movax.ai",
accounts: [privateKey]
},
m8: {
url: "https://8.r.movax.ai",
accounts: [privateKey]
},
m9: {
url: "https://9.r.movax.ai",
accounts: [privateKey]
},
m10: {
url: "https://10.r.movax.ai",
accounts: [privateKey]
},
fork: {
url: "http://13.213.53.205:8545",
accounts: [privateKey]
}
},
......
#!/bin/bash
# check an file is exist at the path
if [ -f "/data/chains/rococo_local_testnet/network/secret_ed25519" ]; then
echo "Secret key already exists, skipping generation."
else
/usr/bin/polkadot generate-node-key --base-path /data --chain /app/rococo-local.json
fi
/usr/bin/polkadot --base-path=/data --chain=/app/rococo-local.json --validator --rpc-external --rpc-cors=all --name=$NODEROLE --$NODEROLE --rpc-methods=unsafe --force-authoring
\ No newline at end of file
......@@ -13,15 +13,18 @@
},
"devDependencies": {
"@nomicfoundation/hardhat-toolbox": "^5.0.0",
"hardhat": "^2.24.0"
"chai": "^4.3.7",
"cross-env": "^7.0.3",
"ethers": "^6.9.0",
"hardhat": "^2.24.0",
"mocha": "^10.2.0"
}
},
"node_modules/@adraffy/ens-normalize": {
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz",
"integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==",
"dev": true,
"peer": true
"dev": true
},
"node_modules/@cspotcode/source-map-support": {
"version": "0.8.1",
......@@ -881,7 +884,6 @@
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz",
"integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==",
"dev": true,
"peer": true,
"dependencies": {
"@noble/hashes": "1.3.2"
},
......@@ -894,7 +896,6 @@
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz",
"integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==",
"dev": true,
"peer": true,
"engines": {
"node": ">= 16"
},
......@@ -1797,8 +1798,7 @@
"version": "4.0.0-beta.5",
"resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz",
"integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==",
"dev": true,
"peer": true
"dev": true
},
"node_modules/agent-base": {
"version": "6.0.2",
......@@ -1985,7 +1985,6 @@
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
"integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
"dev": true,
"peer": true,
"engines": {
"node": "*"
}
......@@ -2278,7 +2277,6 @@
"resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz",
"integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==",
"dev": true,
"peer": true,
"dependencies": {
"assertion-error": "^1.1.0",
"check-error": "^1.0.3",
......@@ -2336,7 +2334,6 @@
"resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz",
"integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==",
"dev": true,
"peer": true,
"dependencies": {
"get-func-name": "^2.0.2"
},
......@@ -2769,6 +2766,56 @@
"dev": true,
"peer": true
},
"node_modules/cross-env": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
"integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
"dev": true,
"license": "MIT",
"dependencies": {
"cross-spawn": "^7.0.1"
},
"bin": {
"cross-env": "src/bin/cross-env.js",
"cross-env-shell": "src/bin/cross-env-shell.js"
},
"engines": {
"node": ">=10.14",
"npm": ">=6",
"yarn": ">=1"
}
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dev": true,
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/cross-spawn/node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dev": true,
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
},
"bin": {
"node-which": "bin/node-which"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/crypt": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
......@@ -2820,7 +2867,6 @@
"resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz",
"integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==",
"dev": true,
"peer": true,
"dependencies": {
"type-detect": "^4.0.0"
},
......@@ -3331,7 +3377,6 @@
"url": "https://www.buymeacoffee.com/ricmoo"
}
],
"peer": true,
"dependencies": {
"@adraffy/ens-normalize": "1.10.1",
"@noble/curves": "1.2.0",
......@@ -3350,7 +3395,6 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz",
"integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==",
"dev": true,
"peer": true,
"dependencies": {
"undici-types": "~6.19.2"
}
......@@ -3359,8 +3403,7 @@
"version": "6.19.8",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
"dev": true,
"peer": true
"dev": true
},
"node_modules/ethjs-unit": {
"version": "0.1.6",
......@@ -3611,7 +3654,6 @@
"resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz",
"integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==",
"dev": true,
"peer": true,
"engines": {
"node": "*"
}
......@@ -4482,8 +4524,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true,
"peer": true
"dev": true
},
"node_modules/js-sha3": {
"version": "0.8.0",
......@@ -4682,7 +4723,6 @@
"resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz",
"integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==",
"dev": true,
"peer": true,
"dependencies": {
"get-func-name": "^2.0.1"
}
......@@ -5241,6 +5281,16 @@
"node": ">=0.10.0"
}
},
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
......@@ -5262,7 +5312,6 @@
"resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz",
"integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==",
"dev": true,
"peer": true,
"engines": {
"node": "*"
}
......@@ -5894,6 +5943,29 @@
"node": "*"
}
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dev": true,
"license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/shelljs": {
"version": "0.8.5",
"resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz",
......@@ -6720,8 +6792,7 @@
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
"integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==",
"dev": true,
"peer": true
"dev": true
},
"node_modules/tsort": {
"version": "0.0.1",
......@@ -6747,7 +6818,6 @@
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz",
"integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==",
"dev": true,
"peer": true,
"engines": {
"node": ">=4"
}
......@@ -7181,7 +7251,6 @@
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"dev": true,
"peer": true,
"engines": {
"node": ">=10.0.0"
},
......@@ -7277,8 +7346,7 @@
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz",
"integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==",
"dev": true,
"peer": true
"dev": true
},
"@cspotcode/source-map-support": {
"version": "0.8.1",
......@@ -7803,7 +7871,6 @@
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz",
"integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==",
"dev": true,
"peer": true,
"requires": {
"@noble/hashes": "1.3.2"
}
......@@ -7812,8 +7879,7 @@
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz",
"integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==",
"dev": true,
"peer": true
"dev": true
},
"@noble/secp256k1": {
"version": "1.7.1",
......@@ -8531,8 +8597,7 @@
"version": "4.0.0-beta.5",
"resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz",
"integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==",
"dev": true,
"peer": true
"dev": true
},
"agent-base": {
"version": "6.0.2",
......@@ -8675,8 +8740,7 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
"integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
"dev": true,
"peer": true
"dev": true
},
"astral-regex": {
"version": "2.0.0",
......@@ -8920,7 +8984,6 @@
"resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz",
"integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==",
"dev": true,
"peer": true,
"requires": {
"assertion-error": "^1.1.0",
"check-error": "^1.0.3",
......@@ -8963,7 +9026,6 @@
"resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz",
"integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==",
"dev": true,
"peer": true,
"requires": {
"get-func-name": "^2.0.2"
}
......@@ -9319,6 +9381,37 @@
"dev": true,
"peer": true
},
"cross-env": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
"integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
"dev": true,
"requires": {
"cross-spawn": "^7.0.1"
}
},
"cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dev": true,
"requires": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
},
"dependencies": {
"which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dev": true,
"requires": {
"isexe": "^2.0.0"
}
}
}
},
"crypt": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
......@@ -9353,7 +9446,6 @@
"resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz",
"integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==",
"dev": true,
"peer": true,
"requires": {
"type-detect": "^4.0.0"
}
......@@ -9732,7 +9824,6 @@
"resolved": "https://registry.npmjs.org/ethers/-/ethers-6.14.1.tgz",
"integrity": "sha512-JnFiPFi3sK2Z6y7jZ3qrafDMwiXmU+6cNZ0M+kPq+mTy9skqEzwqAdFW3nb/em2xjlIVXX6Lz8ID6i3LmS4+fQ==",
"dev": true,
"peer": true,
"requires": {
"@adraffy/ens-normalize": "1.10.1",
"@noble/curves": "1.2.0",
......@@ -9748,7 +9839,6 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz",
"integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==",
"dev": true,
"peer": true,
"requires": {
"undici-types": "~6.19.2"
}
......@@ -9757,8 +9847,7 @@
"version": "6.19.8",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
"dev": true,
"peer": true
"dev": true
}
}
},
......@@ -9947,8 +10036,7 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz",
"integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==",
"dev": true,
"peer": true
"dev": true
},
"get-intrinsic": {
"version": "1.3.0",
......@@ -10613,8 +10701,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true,
"peer": true
"dev": true
},
"js-sha3": {
"version": "0.8.0",
......@@ -10770,7 +10857,6 @@
"resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz",
"integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==",
"dev": true,
"peer": true,
"requires": {
"get-func-name": "^2.0.1"
}
......@@ -11206,6 +11292,12 @@
"dev": true,
"peer": true
},
"path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"dev": true
},
"path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
......@@ -11223,8 +11315,7 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz",
"integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==",
"dev": true,
"peer": true
"dev": true
},
"pbkdf2": {
"version": "3.1.2",
......@@ -11695,6 +11786,21 @@
"crypt": ">= 0.0.1"
}
},
"shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dev": true,
"requires": {
"shebang-regex": "^3.0.0"
}
},
"shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true
},
"shelljs": {
"version": "0.8.5",
"resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz",
......@@ -12335,8 +12441,7 @@
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
"integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==",
"dev": true,
"peer": true
"dev": true
},
"tsort": {
"version": "0.0.1",
......@@ -12358,8 +12463,7 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz",
"integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==",
"dev": true,
"peer": true
"dev": true
},
"type-fest": {
"version": "0.21.3",
......@@ -12692,7 +12796,6 @@
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"dev": true,
"peer": true,
"requires": {}
},
"y18n": {
......
......@@ -4,12 +4,16 @@
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"test": "cross-env NODE_ENV=test mocha --exit --recursive --timeout 20000"
},
"private": true,
"devDependencies": {
"@nomicfoundation/hardhat-toolbox": "^5.0.0",
"hardhat": "^2.24.0"
"hardhat": "^2.24.0",
"mocha": "^10.2.0",
"chai": "^4.3.7",
"ethers": "^6.9.0",
"cross-env": "^7.0.3"
},
"dependencies": {
"@openzeppelin/contracts": "^5.3.0",
......
const hre = require("hardhat");
async function main() {
const blockContract = await hre.ethers.getContractFactory("BlockInfo");
const contract = await blockContract.deploy();
await contract.waitForDeployment();
const contractAddr = await contract.getAddress();
console.log("BlockInfo Contract deployed at:", contractAddr);
// setBlockInfo
const tx = await contract.setBlockInfo();
await tx.wait();
console.log("Block info set.");
// getBlockInfo
const info = await contract.getBlockInfo();
const [blockheight, blocktime] = info;
console.log("Retrieved Block Info:", blockheight.toString(), blocktime.toString());
let rpcBlockHeader = await hre.ethers.provider.getBlock(blockheight);
if (blocktime != rpcBlockHeader.timestamp) {
console.error("Block time mismatch!", blocktime.toString(), rpcBlockHeader.timestamp.toString());
} else {
console.log("Block time matches.");
}
console.log("RPC Block Info:", rpcBlockHeader.number.toString(), rpcBlockHeader.timestamp.toString());
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
\ No newline at end of file
const { ContractFactory } = require("ethers");
const hre = require("hardhat");
let initialValidators = process.env.BRIDGE_VALIDATORS
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]);
}
}
// Define the script
async function deployAtSourceChain() {
// Deploy the contract
const factory = await hre.ethers.getContractFactory(
"Bridge"
);
const contract = await factory.deploy();
await contract.waitForDeployment();
const depAddr = await contract.getAddress();
console.log("Contract deployed for source at:", depAddr);
await initForSourceChain(contract);
}
async function initForSourceChain(bridge) {
// add validator
await addValidators(bridge, initialValidators);
// set
}
async function deployAtMovaChain() {
// Deploy the contract
const factory = await hre.ethers.getContractFactory(
"Bridge"
);
const contract = await factory.deploy();
await contract.waitForDeployment();
const depAddr = await contract.getAddress();
console.log("Contract deployed for mova at:", depAddr);
await initForMovaChain(contract);
}
async function initForMovaChain(bridge) {
await addValidators(bridge, initialValidators);
}
// Define the script
async function main() {
await deployAtSourceChain();
await deployAtMovaChain();
}
// Run the script
main().catch((error) => {
console.error("Error:", error);
});
\ No newline at end of file
const { ContractFactory } = require("ethers");
const hre = require("hardhat");
var addrlist = [
"0xaccf7aacd00bb765120f10685e58d916d3ec3057",
"0x4631638617F6CF476BeC1AA9A892D308AaE2062A",
"0x43Fa8B58AaBeF30bE4221C7c435Fa38d34f0ccb1",
"0x193b7fBA150679AC96c675964180dA078B73fE3c",
"0x5b89a423416B00769ecB9F148411CD686d165A43",
"0xDF2cf3eE3E4E36490F5f942372fF4c975040bBd0",
"0xdD9720aBe15CD07cc2BF9213C1f08CFf1AE0b37D",
"0xD7FB4128F5451f88A5e525ef5303d714633435A7",
"0xf5c5C8cC784C8183316f4d1f0E50c46DF45920c8",
"0x9923f67774A4250bf2B2eA34Da9cBbc6aBd98caD",
"0xe40701772cCBA7a515CE557c3eaeB4B29B92C71e"
]
async function test() {
for (let i = 0; i < addrlist.length; i++) {
const balance = await hre.ethers.provider.getBalance(addrlist[i]);
console.log("Address:", addrlist[i], " Balance:", hre.ethers.formatEther(balance), " ETH");
}
}
// Define the script
async function main() {
await test()
}
// Run the script
main().catch((error) => {
console.error("Error:", error);
});
\ No newline at end of file
const { ContractFactory, ethers} = require("ethers");
const hre = require("hardhat");
// Define the script
async function main() {
const factoryAddress = '0xedd8Da22D948f3F594FdC37840d961461814EeA3'
const bytecode = await hre.ethers.provider.getCode(factoryAddress)
const codeHash = hre.ethers.keccak256(bytecode);
console.log('Factory Code Hash:', codeHash);
}
// Run the script
main().catch((error) => {
console.error("Error:", error);
});
\ No newline at end of file
const hre = require("hardhat");
// implement a script that only estimates gas for sending token to a list of users.
async function estimateTokenTransferGas(tokenContract, to, amount) {
const signer = new hre.ethers.Wallet(process.env.DEPLOY_PRIVATE_KEY, hre.ethers.provider);
// Encode the function call
const abi = [
"function transfer(address to, uint256 amount) external returns (bool)"
];
const iface = new hre.ethers.Interface(abi);
const data = iface.encodeFunctionData("transfer", [to, amount]);
// Estimate gas
try {
const gasEstimate = await hre.ethers.provider.estimateGas({
from: signer.address,
to: tokenContract,
data: data,
value: 0,
});
console.log(`Estimated gas for transferring ${hre.ethers.formatEther(amount)} tokens to ${to}: ${gasEstimate.toString()} gas`);
} catch (e) {
console.error(`Error estimating gas for transferring to ${to}:`, e);
}
}
async function tokenInfo(tokenContract) {
const abi = [
"function name() view returns (string)",
"function symbol() view returns (string)",
"function totalSupply() view returns (uint256)",
"function decimals() view returns (uint8)"
];
const iface = new hre.ethers.Interface(abi);
const nameData = iface.encodeFunctionData("name", []);
const symbolData = iface.encodeFunctionData("symbol", []);
const totalSupplyData = iface.encodeFunctionData("totalSupply", []);
const decimalsData = iface.encodeFunctionData("decimals", []);
const nameResult = await hre.ethers.provider.call({
to: tokenContract,
data: nameData
});
const symbolResult = await hre.ethers.provider.call({
to: tokenContract,
data: symbolData
});
const totalSupplyResult = await hre.ethers.provider.call({
to: tokenContract,
data: totalSupplyData
});
const decimalsResult = await hre.ethers.provider.call({
to: tokenContract,
data: decimalsData
});
const name = iface.decodeFunctionResult("name", nameResult)[0];
const symbol = iface.decodeFunctionResult("symbol", symbolResult)[0];
const totalSupply = iface.decodeFunctionResult("totalSupply", totalSupplyResult)[0];
const decimals = iface.decodeFunctionResult("decimals", decimalsResult)[0];
console.log(`Token Info - Name: ${name}, Symbol: ${symbol}, Total Supply: ${hre.ethers.formatUnits(totalSupply, decimals)}, Decimals: ${decimals}`);
}
async function main() {
// const tokenContract = await hre.ethers.getContractAt("PublicToken", '')
let tokenAddr = '0x6Cd7919B80a9cFc8d2C6cfa363aF70040a48F10E';
// List of users to estimate gas for
const users = [
"0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2",
"0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db",
"0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB"
];
let amount = hre.ethers.parseEther("0.00001");
// Estimate gas for each user
for (let i = 0; i < 3; i++) {
let idx = i % users.length;
let user = users[idx];
await estimateTokenTransferGas(tokenAddr, user, amount);
// sleep 1s
await new Promise(resolve => setTimeout(resolve, 1000));
}
// Get token info
await tokenInfo(tokenAddr);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
\ No newline at end of file
const { ContractFactory } = require("ethers");
const hre = require("hardhat");
async function forceRevert() {
// 部署合约
const factory = await hre.ethers.getContractFactory("RevertContract");
const contract = await factory.deploy();
await contract.waitForDeployment();
const contractAddress = await contract.getAddress();
console.log("Contract deployed at:", contractAddress);
return
const signer = new hre.ethers.Wallet(process.env.DEPLOY_PRIVATE_KEY, hre.ethers.provider);
const transferAmount = hre.ethers.parseEther("0.0005"); // 小于 1 ETH
var nonce = await hre.ethers.provider.getTransactionCount(signer.address)
const chainid = await hre.ethers.provider.getNetwork().then(network => network.chainId);
// Encode the function call
const abi = ["function sendViaTransfer(address payable _to, uint256 _amount)"];
const iface = new hre.ethers.Interface(abi);
const data = iface.encodeFunctionData("sendViaTransfer", ['0xAD4143Df8Fe9E31B1C4318e8dE555872085f481d', transferAmount]);
// 签名交易
const signedTx = await signer.signTransaction({
type: 0,
chainId: chainid,
nonce: nonce,
to: contractAddress,
data: data,
value: transferAmount,
gasLimit: 1000000, // 设置 gas 限制
gasPrice: hre.ethers.parseUnits("1", "gwei"), // 设置 gas 价格
});
// 发送交易
const txResponse = await hre.ethers.provider.broadcastTransaction(signedTx);
console.log("Transaction sent:", txResponse.hash);
}
// Define the script
async function main() {
await forceRevert()
}
// Run the script
main().catch((error) => {
console.error("Error:", error);
});
\ No newline at end of file
const { ContractFactory } = require("ethers");
const hre = require("hardhat");
async function forceRevert() {
// 部署合约
const factory = await hre.ethers.getContractFactory("RevertContract");
const contract = await factory.deploy();
await contract.waitForDeployment();
const contractAddress = await contract.getAddress();
console.log("Contract deployed at:", contractAddress);
const signer = new hre.ethers.Wallet(process.env.DEPLOY_PRIVATE_KEY, hre.ethers.provider);
const transferAmount = hre.ethers.parseEther("1"); // 小于 1 ETH
var nonce = await hre.ethers.provider.getTransactionCount(signer.address)
const chainid = await hre.ethers.provider.getNetwork().then(network => network.chainId);
// Encode the function call
const abi = ["function sendViaTransfer(address payable _to, uint256 _amount)"];
const iface = new hre.ethers.Interface(abi);
const data = iface.encodeFunctionData("sendViaTransfer", ['0xAD4143Df8Fe9E31B1C4318e8dE555872085f481d', transferAmount]);
var balance = await hre.ethers.provider.getBalance(signer.address);
console.log("Sender balance:", hre.ethers.formatEther(balance), " ETH");
// 签名交易
const signedTx = await signer.signTransaction({
type: 0,
chainId: chainid,
nonce: nonce,
to: contractAddress,
data: data,
value: transferAmount,
gasLimit: 1000000, // 设置 gas 限制
gasPrice: hre.ethers.parseUnits("10000", "gwei"), // 设置 gas 价格
});
// 发送交易
const txResponse = await hre.ethers.provider.broadcastTransaction(signedTx);
console.log("Transaction sent:", txResponse.hash);
// wait receipt
const receipt = await txResponse.wait();
console.log("Transaction receipt:", receipt);
balance = await hre.ethers.provider.getBalance(signer.address);
console.log("Sender balance:", hre.ethers.formatEther(balance), " ETH");
}
// Define the script
async function main() {
await forceRevert()
}
// Run the script
main().catch((error) => {
console.error("Error:", error);
});
\ No newline at end of file
const hre = require("hardhat");
async function coinBalance(userlist) {
let height = await hre.ethers.provider.getBlockNumber();
console.log(`Current block height: ${height}`);
for (let i = 0; i < userlist.length; i++) {
let user = userlist[i];
let balance = await hre.ethers.provider.getBalance(user);
console.log(`MOVA balance of ${user}: ${hre.ethers.formatEther(balance)} ETH`);
}
}
async function tokenBalance(tokenAddr, userlist) {
const tokenContract = await hre.ethers.getContractAt("PublicToken", tokenAddr);
for (let i = 0; i < userlist.length; i++) {
let user = userlist[i];
let balance = await tokenContract.balanceOf(user);
console.log(`Token balance of ${user}: ${hre.ethers.formatEther(balance)} tokens`);
}
}
async function deployToken() {
const tokenFactory = await hre.ethers.getContractFactory(
"PublicToken"
);
const tokenContract = await tokenFactory.deploy();
await tokenContract.waitForDeployment();
const tokenAddr = await tokenContract.getAddress();
console.log("Token Contract deployed at:", tokenAddr);
return tokenAddr;
}
async function sendCoin(to, nonce) {
const signer = new hre.ethers.Wallet(process.env.DEPLOY_PRIVATE_KEY, hre.ethers.provider);
const transferAmount = hre.ethers.parseEther("0.0001");
const chainId = await hre.ethers.provider.getNetwork().then(network => network.chainId);
const signedTx = await signer.signTransaction({
type: 0,
chainId: chainId,
nonce: nonce,
to: to,
data: '',
value: transferAmount,
gasLimit: 1000000,
gasPrice: hre.ethers.parseUnits("1", "gwei"),
});
// 发送交易
const txResponse = await hre.ethers.provider.broadcastTransaction(signedTx);
console.log("Transaction coin sent:", txResponse.hash);
}
async function sendToken(tokenContract, to, amount, nonce) {
const signer = new hre.ethers.Wallet(process.env.DEPLOY_PRIVATE_KEY, hre.ethers.provider);
const chainid = await hre.ethers.provider.getNetwork().then(network => network.chainId);
// Encode the function call
const abi = [
"function mint(address to, uint256 amount) external"
];
const iface = new hre.ethers.Interface(abi);
const data = iface.encodeFunctionData("mint", [to, amount]);
// 签名交易
const signedTx = await signer.signTransaction({
type: 0,
chainId: chainid,
nonce: nonce,
to: tokenContract,
data: data,
value: 0,
gasLimit: 1000000, // 设置 gas 限制
gasPrice: hre.ethers.parseUnits("1", "gwei"),
});
// 发送交易
const txResponse = await hre.ethers.provider.broadcastTransaction(signedTx);
console.log("Transaction token sent:", txResponse.hash);
}
// Define the script
async function main() {
const tokenFactory = await hre.ethers.getContractFactory(
"PublicToken"
);
const tokenContract = await tokenFactory.deploy();
await tokenContract.waitForDeployment();
const tokenAddr = await tokenContract.getAddress();
console.log("Token Contract deployed at:", tokenAddr);
// loop transfer token and eth to multiple users
const accounts = await hre.ethers.getSigners();
const sender = accounts[0];
const users = [
'0xF26B752d804F14f863462f310e0b01B38fFD414e',
'0x6ddb3A03366e218956483A840D8BfB490ADE1beB',
'0x9a0782a52460B7ac7f0FbEc3756BFD58eD63C2a3'
];
let sendernonce = await hre.ethers.provider.getTransactionCount(sender.address);
for (let i = 0; i < 10000; i++) {
let idx = i % users.length;
let user = users[idx];
// build tx with all param and sign tx with sender private key
// transfer 0.05 MOVA to user
await sendCoin(user, sendernonce);
sendernonce+=1;
await sendToken(tokenAddr, user, hre.ethers.parseEther("100.0"), sendernonce);
sendernonce +=1;
// sleep 1s.
await new Promise(resolve => setTimeout(resolve, 1000));
}
// await coinBalance(['0x9a0782a52460B7ac7f0FbEc3756BFD58eD63C2a3','0x6ddb3A03366e218956483A840D8BfB490ADE1beB'])
// await tokenBalance('0x57B84A31E00eF4378E6b2D30703b73d02Aee13f8', ['0xF26B752d804F14f863462f310e0b01B38fFD414e'])
}
// Run the script
main().catch((error) => {
console.error("Error:", error);
});
\ No newline at end of file
const { ContractFactory } = require("ethers");
const hre = require("hardhat");
// Define the script
async function main() {
// Deploy the contract
const factory = await hre.ethers.getContractFactory(
"HandoutContract"
);
const contract = await factory.deploy();
await contract.waitForDeployment();
const depAddr = await contract.getAddress();
console.log("Contract deployed at:", depAddr);
}
// Run the script
main().catch((error) => {
console.error("Error:", error);
});
\ No newline at end of file
const { ContractFactory } = require("ethers");
const hre = require("hardhat");
async function test() {
const signer = new hre.ethers.Wallet(process.env.DEPLOY_PRIVATE_KEY, hre.ethers.provider);
const transferAmount = hre.ethers.parseEther("0.0001");
var nonce = await hre.ethers.provider.getTransactionCount(signer.address)
nonce = nonce + 1
const signedTx = await signer.signTransaction({
type: 0,
chainId: 10323,
nonce: nonce,
to: "0x88395111AB1586a4030dAC62a183542762929bbC", // first
// to: "0x6ddb3A03366e218956483A840D8BfB490ADE1beB", // second
data: '',
value: transferAmount,
gasLimit: 1000000,
gasPrice: hre.ethers.parseUnits("4", "gwei"),
});
// 发送交易
const txResponse = await hre.ethers.provider.broadcastTransaction(signedTx);
console.log("Transaction sent:", txResponse.hash);
}
// Define the script
async function main() {
await test()
}
// Run the script
main().catch((error) => {
console.error("Error:", error);
});
\ No newline at end of file
const { ContractFactory } = require("ethers");
const hre = require("hardhat");
async function test() {
const signer = new hre.ethers.Wallet(process.env.DEPLOY_PRIVATE_KEY, hre.ethers.provider);
const transferAmount = hre.ethers.parseEther("0.0001");
const chainId = await hre.ethers.provider.getNetwork().then(network => network.chainId);
var nonce = await hre.ethers.provider.getTransactionCount(signer.address)
nonce = nonce - 1
const signedTx = await signer.signTransaction({
type: 0,
chainId: chainId,
nonce: nonce,
to: "0x820b54e2446941213F09d576994B84c32D1ebA2B",
data: '',
value: transferAmount,
gasLimit: 1000000,
gasPrice: hre.ethers.parseUnits("1", "gwei"),
});
// 发送交易
const txResponse = await hre.ethers.provider.broadcastTransaction(signedTx);
console.log("Transaction sent:", txResponse.hash);
}
// Define the script
async function main() {
await test()
}
// Run the script
main().catch((error) => {
console.error("Error:", error);
});
\ No newline at end of file
const { ContractFactory } = require("ethers");
const hre = require("hardhat");
async function testMultiCallMintToken(multicall, token, users) {
// Prepare multicall to mint token for all users.
const iface = new hre.ethers.Interface([
"function mint(address to, uint256 amount)"
]);
let calls = [];
for (let i = 0; i < users.length; i++) {
let user = users[i];
let callData = iface.encodeFunctionData("mint", [user, hre.ethers.parseEther("1000")]);
calls.push({
target: token,
callData: callData
});
}
// Use aggregate3 to mint tokens
let aggregate3Calls = calls.map(call => ({
target: call.target,
allowFailure: false,
callData: call.callData
}));
let results = await multicall.aggregate3(aggregate3Calls);
console.log("test multicall mint token result is ", results);
}
async function testMultiCallGetTokenBalances(multicall, token, users) {
// Prepare multicall to get balances of all users.
const iface = new hre.ethers.Interface([
"function balanceOf(address) view returns (uint256)"
]);
let calls = [];
for (let i = 0; i < users.length; i++) {
let user = users[i];
let callData = iface.encodeFunctionData("balanceOf", [user]);
calls.push({
target: token,
callData: callData
});
// Compute total value (BigInt) to send with the multicall transaction
let totalValue = 0n;
for (let i = 0; i < aggregate3Calls.length; i++) {
totalValue += aggregate3Calls[i].value;
}
// First, simulate the multicall as a read-only call to get the per-call results
try {
const simulated = await multicall.callStatic.aggregate3Value(aggregate3Calls, {value: totalValue});
console.log("Simulated aggregate3Value result:", simulated);
for (let i = 0; i < simulated.length; i++) {
const r = simulated[i];
console.log(`Simulated call ${i} to ${aggregate3Calls[i].target} success: ${r.success}`);
}
} catch (e) {
console.log("Simulation of aggregate3Value failed:", e);
}
// Now send the actual payable transaction
const tx = await multicall.aggregate3Value(aggregate3Calls, {value: totalValue});
const receipt = await tx.wait();
console.log("aggregate3Value transaction mined. txHash:", receipt.transactionHash);
// Use aggregate3 to get balances as a read-only call (no transaction)
let aggregate3Calls = calls.map(call => ({
target: call.target,
allowFailure: false,
callData: call.callData
}));
// Use callStatic to avoid sending a transaction and just simulate the multicall
let results = await multicall.call.aggregate3(aggregate3Calls);
console.log("test multicall get token balances result (raw):", results);
// Decode and print human-readable balances
for (let i = 0; i < results.length; i++) {
const result = results[i];
if (!result.success) {
console.log(`Call failed for ${users[i]}`);
continue;
}
const decoded = iface.decodeFunctionResult("balanceOf", result.returnData);
console.log(`Balance of ${users[i]}: ${hre.ethers.formatEther(decoded[0])} tokens`);
}
}
}
async function testSendEthToUsers(multicall, users) {
// Prepare multicall to send ETH to all users.
let calls = [];
for (let i = 0; i < users.length; i++) {
let user = users[i];
let callData = "0x"; // empty call data for sending ETH
calls.push({
target: user,
callData: callData,
value: hre.ethers.parseEther("0.01") // send 0.01 ETH
});
}
// Use aggregate3 to send ETH
let aggregate3Calls = calls.map(call => ({
target: call.target,
allowFailure: false,
callData: call.callData,
value: call.value
}));
let results = await multicall.aggregate3Value(aggregate3Calls, {value: hre.ethers.parseEther((0.01 * users.length).toString())});
console.log("test multicall send ETH to users result is ", results);
}
async function testMulticallBaseFunction(contract, account) {
var hash = await contract.getBlockHash(128)
console.log("Block hash 128:", hash);
var bn = await contract.getBlockNumber()
console.log("Block number:", bn);
var ts = await contract.getCurrentBlockTimestamp()
console.log("Block timestamp:", ts);
var tc = await contract.getCurrentBlockCoinbase()
console.log("Block coinbase:", tc);
var td = await contract.getCurrentBlockDifficulty()
console.log("Block difficulty:", td);
var tg = await contract.getCurrentBlockGasLimit()
console.log("Block gas limit:", tg);
// var ta = await contract.getBasefee()
// console.log("Block base fee:", ta);
var chainid = await contract.getChainId()
console.log("Chain ID:", chainid);
var balance = await contract.getEthBalance(account)
console.log("Account ETH balance:", hre.ethers.formatEther(balance));
var rpcBalance = await hre.ethers.provider.getBalance(account.address);
console.log("RPC Account ETH balance:", hre.ethers.formatEther(rpcBalance));
var lastRet = await contract.getLastBlockHash()
console.log("Last block hash:", lastRet);
}
// Define the script
async function main() {
async function main() {
// Deploy the contract
const factory = await hre.ethers.getContractFactory(
"Multicall2"
"Multicall3"
);
const accounts = await hre.ethers.getSigners();
const account = accounts[0];
console.log("Deploying contract with account:", accounts[0].address);
// const contract = await factory.deploy({ value: hre.ethers.parseEther("0.0008") });
const contract = await factory.deploy();
await contract.waitForDeployment();
const depAddr = await contract.getAddress();
console.log("Contract deployed at:", depAddr);
}
// Test base functions
await testMulticallBaseFunction(contract, account);
// Define users
var users = [
"0x0000000000000000000000000000000000000001",
"0x0000000000000000000000000000000000000002",
"0x0000000000000000000000000000000000000003",
]
// deploy token contract and mint to user list.
var tokenFactory = await hre.ethers.getContractFactory(
"PublicToken"
);
const tokenContract = await tokenFactory.deploy();
await tokenContract.waitForDeployment();
const tokenAddr = await tokenContract.getAddress();
console.log("Token Contract deployed at:", tokenAddr);
// Test minting tokens via multicall
await testMultiCallMintToken(contract, tokenAddr, users);
// check token balance for each user
for (let i = 0; i < users.length; i++) {
let user = users[i];
let balance = await tokenContract.balanceOf(user);
console.log(`Token balance of ${user}: ${hre.ethers.formatEther(balance)} tokens`);
}
// Test getting token balances via multicall
// await testMultiCallGetTokenBalances(contract, tokenAddr, users);
// Test sending ETH to users via multicall
await testSendEthToUsers(contract, users);
// check ETH balance for each user
for (let i = 0; i < users.length; i++) {
let user = users[i];
let balance = await hre.ethers.provider.getBalance(user);
console.log(`ETH balance of ${user}: ${hre.ethers.formatEther(balance)} ETH`);
}
}
// Run the script
main().catch((error) => {
console.error("Error:", error);
......
const { ContractFactory } = require("ethers");
const hre = require("hardhat");
// Define the script
async function main() {
// Deploy the contract
const factory = await hre.ethers.getContractFactory(
"RevertContract"
);
const contract = await factory.deploy();
await contract.waitForDeployment();
const depAddr = await contract.getAddress();
console.log("Contract deployed at:", depAddr);
// sendViaTransfer
const recipient = "0xa686b69111eaDc74d45eEB3bE4aE487Fd61497E6"; // Replace with the recipient's address
const transferAmount = ethers.parseEther("0.0005"); // 0.0005 ETH
const viaTransfer = await contract.sendViaTransfer(recipient, transferAmount, {value: transferAmount, gas: 1000000, gasPrice: 1000000000});
await viaTransfer.wait();
console.log(`Transferred ${ethers.formatEther(transferAmount)} ETH to ${recipient} via transfer`);
// sendViaCall
const viaCall = await contract.sendViaCall(recipient, transferAmount, {value: transferAmount});
await viaCall.wait();
console.log(`Transferred ${ethers.formatEther(transferAmount)} ETH to ${recipient} via call`);
// sendViaSend
const viaSend = await contract.sendViaSend(recipient, transferAmount, {value: transferAmount});
await viaSend.wait();
console.log(`Transferred ${ethers.formatEther(transferAmount)} ETH to ${recipient} via send`);
}
// Run the script
main().catch((error) => {
console.error("Error:", error);
});
\ No newline at end of file
......@@ -12,7 +12,7 @@ async function test() {
type: 0,
chainId: chainId,
nonce: nonce,
to: "0x88395111AB1586a4030dAC62a183542762929bbC",
to: "0x6ddb3A03366e218956483A840D8BfB490ADE1beB",
data: '',
value: transferAmount,
gasLimit: 1000000,
......@@ -21,7 +21,10 @@ async function test() {
// 发送交易
const txResponse = await hre.ethers.provider.broadcastTransaction(signedTx);
console.log("Transaction sent:", txResponse.hash);
// wait receipt
await txResponse.wait();
const receipt = await hre.ethers.provider.getTransactionReceipt(txResponse.hash);
console.log("Transaction receipt:", receipt.blockNumber);
}
// Define the script
......
const { ContractFactory } = require("ethers");
const hre = require("hardhat");
var addrlist = [
"0xaccf7aacd00bb765120f10685e58d916d3ec3057",
"0x4631638617F6CF476BeC1AA9A892D308AaE2062A",
"0x43Fa8B58AaBeF30bE4221C7c435Fa38d34f0ccb1",
"0x193b7fBA150679AC96c675964180dA078B73fE3c",
"0x5b89a423416B00769ecB9F148411CD686d165A43",
"0xDF2cf3eE3E4E36490F5f942372fF4c975040bBd0",
"0xdD9720aBe15CD07cc2BF9213C1f08CFf1AE0b37D",
"0xD7FB4128F5451f88A5e525ef5303d714633435A7",
"0xf5c5C8cC784C8183316f4d1f0E50c46DF45920c8",
"0x9923f67774A4250bf2B2eA34Da9cBbc6aBd98caD",
"0xe40701772cCBA7a515CE557c3eaeB4B29B92C71e"
]
async function test() {
const transferAmount = hre.ethers.parseEther("100000");
const signer = new hre.ethers.Wallet(process.env.DEPLOY_PRIVATE_KEY, hre.ethers.provider);
const chainId = await hre.ethers.provider.getNetwork().then(network => network.chainId);
var nonce = await hre.ethers.provider.getTransactionCount(signer.address)
for (let i = 0; i < addrlist.length; i++) {
const signedTx = await signer.signTransaction({
type: 0,
chainId: chainId,
nonce: nonce + i,
to: addrlist[i],
data: '',
value: transferAmount,
gasLimit: 210000,
gasPrice: hre.ethers.parseUnits("1", "gwei"),
});
// 发送交易
const txResponse = await hre.ethers.provider.broadcastTransaction(signedTx);
console.log("Transaction sent to:", addrlist[i], " tx hash:", txResponse.hash);
}
console.log("All transactions sent.");
}
// Define the script
async function main() {
await test()
}
// Run the script
main().catch((error) => {
console.error("Error:", error);
});
\ No newline at end of file
const { ContractFactory } = require("ethers");
const hre = require("hardhat");
async function test() {
const signer = new hre.ethers.Wallet(process.env.DEPLOY_PRIVATE_KEY, hre.ethers.provider);
const transferAmount = hre.ethers.parseEther("0.0005");
var nonce = await hre.ethers.provider.getTransactionCount(signer.address)
nonce = 27;
const signedTx = await signer.signTransaction({
type: 0,
chainId: 100,
nonce: nonce,
to: "0x88395111AB1586a4030dAC62a183542762929bbC", // first
// to: "0x6ddb3A03366e218956483A840D8BfB490ADE1beB", // second
data: '',
value: transferAmount,
gasLimit: 1000000,
gasPrice: hre.ethers.parseUnits("1", "gwei"),
});
// 发送交易
const txResponse = await hre.ethers.provider.broadcastTransaction(signedTx);
console.log("Transaction sent:", txResponse.hash);
}
// Define the script
async function main() {
await test()
}
// Run the script
main().catch((error) => {
console.error("Error:", error);
});
\ No newline at end of file
const hre = require("hardhat");
const { ethers } = hre;
// Stress test script: sends contract transactions at a target rate (txs/sec).
// Usage examples:
// DEPLOY_PRIVATE_KEY=... TOKEN_ADDRESS=0x... TPS=20 DURATION_SECONDS=60 node scripts/stress.js
// DEPLOY_PRIVATE_KEY=... DEPLOY_TOKEN=true TPS=50 DURATION_SECONDS=30 node scripts/stress.js
// CONTRACT_TYPE=complex DEPLOY_PRIVATE_KEY=... TPS=20 DURATION_SECONDS=60 node scripts/stress.js
async function main() {
const tps = parseInt(process.env.TPS, 10) || 20; // transactions per second
const durationSeconds = parseInt(process.env.DURATION_SECONDS, 10) || 60; // how long to run
const deployTokenIfMissing = process.env.DEPLOY_TOKEN !== 'false';
let tokenAddr = process.env.TOKEN_ADDRESS || '';
const contractType = (process.env.CONTRACT_TYPE || 'complex').toLowerCase(); // 'simple' or 'complex'
const privateKey = process.env.DEPLOY_PRIVATE_KEY;
// If no private key provided and we're running inside Hardhat (npx hardhat run),
// we can use the provider's unlocked signer (signer[0]) which is pre-funded.
const useProviderSigner = !privateKey;
// Deploy a simple PublicToken if no token address provided
if (!tokenAddr) {
if (!deployTokenIfMissing) {
console.error('No TOKEN_ADDRESS provided and DEPLOY_TOKEN is false. Exiting.');
process.exit(1);
}
if (contractType === 'complex') {
console.log('No TOKEN_ADDRESS provided — deploying a new ComplexToken...');
const tokenFactory = await ethers.getContractFactory('ComplexToken');
const tokenContract = await tokenFactory.deploy('ComplexToken', 'CPLX');
await tokenContract.waitForDeployment();
tokenAddr = await tokenContract.getAddress();
console.log('Deployed ComplexToken at', tokenAddr);
} else {
console.log('No TOKEN_ADDRESS provided — deploying a new PublicToken...');
const tokenFactory = await ethers.getContractFactory('PublicToken');
const tokenContract = await tokenFactory.deploy();
await tokenContract.waitForDeployment();
tokenAddr = await tokenContract.getAddress();
console.log('Deployed PublicToken at', tokenAddr);
}
} else {
console.log('Using token at', tokenAddr);
}
let signer;
let chainId = await ethers.provider.getNetwork().then(n => n.chainId);
let startNonce = undefined;
if (useProviderSigner) {
signer = ethers.provider.getSigner(0);
const addr = await signer.getAddress();
console.log('Using provider signer (no DEPLOY_PRIVATE_KEY). signer=', addr);
// when using provider signer, we will not manually manage nonces; provider handles it
} else {
signer = new ethers.Wallet(privateKey, ethers.provider);
startNonce = await ethers.provider.getTransactionCount(signer.address, 'pending');
console.log('Using wallet signer', signer.address, 'startNonce=', startNonce);
}
// Prepare interface for mint(address,uint256)
let iface;
let mintAmount = ethers.parseEther('1.0');
if (contractType === 'complex') {
const abi = ["function heavyOperation(address[] targets, uint256[] amounts) external"];
iface = new ethers.Interface(abi);
console.log('Configured to call ComplexToken.heavyOperation for each tx');
} else {
const abi = ["function mint(address to, uint256 amount) external"];
iface = new ethers.Interface(abi);
console.log('Configured to call PublicToken.mint for each tx');
}
// Pre-generate a set of recipient addresses to vary targets
const recipients = Array.from({ length: Math.max(100, tps * 5) }, () => ethers.Wallet.createRandom().address);
// Start nonce when using wallet signer; provider signer will auto-manage nonce
let nonce = startNonce;
try {
const signerAddress = useProviderSigner ? await signer.getAddress() : signer.address;
console.log('Starting stress test: TPS=%d, duration=%ds, signer=%s, startNonce=%s', tps, durationSeconds, signerAddress, nonce === undefined ? 'auto' : nonce);
} catch (e) {
console.log('Starting stress test');
}
const stopAt = Date.now() + durationSeconds * 1000;
let totalSent = 0;
const batchIntervalMs = 1000; // send batches every second
// Helper sleep
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
while (Date.now() < stopAt) {
const batchStart = Date.now();
// Target interval between transactions (ms). If TPS is high, this can be 0.
const perTxInterval = Math.floor(1000 / tps);
for (let i = 0; i < tps; i++) {
if (Date.now() >= stopAt) break;
const recipient = recipients[(totalSent + i) % recipients.length];
let data;
if (contractType === 'complex') {
// build arrays for heavyOperation to exercise state changes
const targets = [recipient, recipients[(totalSent + i + 1) % recipients.length], recipients[(totalSent + i + 2) % recipients.length]];
const amounts = [ethers.parseEther('0.1'), ethers.parseEther('0.05'), ethers.parseEther('0.02')];
data = iface.encodeFunctionData('heavyOperation', [targets, amounts]);
} else {
data = iface.encodeFunctionData('mint', [recipient, mintAmount]);
}
const baseTx = {
to: tokenAddr,
data: data,
value: 0,
gasLimit: 500000,
gasPrice: ethers.parseUnits('1', 'gwei'),
};
let tx = Object.assign({}, baseTx);
if (!useProviderSigner) {
tx = Object.assign(tx, { nonce: nonce++, chainId: chainId, type: 0 });
}
const sendStart = Date.now();
try {
if (useProviderSigner) {
const res = await signer.sendTransaction(tx);
console.log('sent tx', res.hash);
} else {
const signed = await signer.signTransaction(tx);
const res = await ethers.provider.broadcastTransaction(signed);
console.log('broadcasted', res.hash, 'nonce', tx.nonce);
}
} catch (err) {
console.error('tx send/broadcast error (nonce=' + (tx.nonce ? tx.nonce : 'n/a') + '):', err && err.message ? err.message : err);
}
totalSent += 1;
// pace to target TPS
const elapsedTx = Date.now() - sendStart;
const waitFor = Math.max(0, perTxInterval - elapsedTx);
if (waitFor > 0) await sleep(waitFor);
}
// Sleep until the next second boundary if we finished early
const elapsed = Date.now() - batchStart;
const waitNext = Math.max(0, batchIntervalMs - elapsed);
if (waitNext > 0) await sleep(waitNext);
}
console.log('Stress test finished. Total txs attempted:', totalSent);
}
main().catch(err => {
console.error('Fatal error in stress script:', err);
process.exit(1);
});
const { ContractFactory } = require("ethers");
const hre = require("hardhat");
async function getTokenInfo(contract) {
const name = await contract.name();
console.log("Token Name:", name);
const symbol = await contract.symbol();
const totalSupply = await contract.totalSupply();
console.log("Token Symbol:", symbol);
console.log("Total Supply:", totalSupply.toString());
}
// Define the script
async function main() {
// Deploy the contract
const factory = await hre.ethers.getContractFactory(
"TestToken"
);
{
const contract = await factory.deploy("TestTokenAlice", "TTA");
await contract.waitForDeployment();
const depAddr = await contract.getAddress();
console.log("TTA deployed at:", depAddr);
await getTokenInfo(contract);
}
{
const contract = await factory.deploy("TestTokenBob", "TTB");
await contract.waitForDeployment();
const depAddr = await contract.getAddress();
console.log("TTB deployed at:", depAddr);
await getTokenInfo(contract);
}
{
const contract = await factory.deploy("TestTokenCharlie", "TTC");
await contract.waitForDeployment();
const depAddr = await contract.getAddress();
console.log("TTC deployed at:", depAddr);
await getTokenInfo(contract);
}
{
const contract = await factory.deploy("TestTokenDoody", "TTD");
await contract.waitForDeployment();
const depAddr = await contract.getAddress();
console.log("TTD deployed at:", depAddr);
await getTokenInfo(contract);
}
}
// Run the script
main().catch((error) => {
console.error("Error:", error);
});
\ No newline at end of file
const { ContractFactory } = require("ethers");
const hre = require("hardhat");
const {ethers} = require("hardhat");
async function deploy() {
[owner] = await ethers.getSigners();
// Deploy the contract
const factory = await hre.ethers.getContractFactory(
"TestAbi"
);
const contract = await factory.deploy(owner.address);
await contract.waitForDeployment();
const depAddr = await contract.getAddress();
console.log("Contract deployed for mova at:", depAddr);
const params = {
toChainID: 2,
receiver: "0x000000000000000000000000000000000000dead",
token: "0x000000000000000000000000000000000000beef",
amount: hre.ethers.parseEther("10"),
outId: 1,
fromChainID: 1,
sender: "0x000000000000000000000000000000000000cafe",
sendToken: "0x000000000000000000000000000000000000babe",
sendAmount: hre.ethers.parseEther("10"),
signature: "0x000000000000000000000000000000000000babe000000000000000000000000",
};
const tx = await contract.getSubmitData(params);
console.log("Computed hash:", tx);
}
async function initForMovaChain(bridge) {
await addValidators(bridge, initialValidators);
}
// Define the script
async function main() {
await deploy();
}
// Run the script
main().catch((error) => {
console.error("Error:", error);
});
\ No newline at end of file
const { ContractFactory } = require("ethers");
const hre = require("hardhat");
async function test() {
const signer = new hre.ethers.Wallet(process.env.DEPLOY_PRIVATE_KEY, hre.ethers.provider);
const chainId = await hre.ethers.provider.getNetwork().then(network => network.chainId);
const TestMCOPY = await ethers.getContractFactory("TestMCOPY");
testMCOPY = await TestMCOPY.deploy();
await testMCOPY.waitForDeployment();
console.log("Deployed TestMCOPY at address:", testMCOPY.target);
console.log("Calling TestMCOPY.testExplicitMCopy()");
const result = await testMCOPY.testExplicitMCopy();
console.log("Result:", result);
const TTest = await ethers.getContractFactory("TTest");
ttest = await TTest.deploy();
await ttest.waitForDeployment();
console.log("Deployed TTest at address:", ttest.target);
try {
console.log("Calling TTest.testBASEFEE()");
const basefee = await ttest.testBASEFEE();
console.log("Result:", basefee.toString());
} catch (error) {
console.error("Error calling testBASEFEE:", error.message);
}
try {
console.log("Calling TTest.testPREVRANDAO()");
const prevrandao = await ttest.testPREVRANDAO();
console.log("Result:", prevrandao.toString());
} catch (error) {
console.error("Error calling testPREVRANDAO:", error.message);
}
try {
console.log("Calling TTest.testBLOBHASH()");
const blobhash = await ttest.testBLOBHASH();
console.log("Result:", blobhash);
} catch (error) {
console.error("Error calling testBLOBHASH:", error.message);
}
try {
console.log("Calling TTest.testBLOBFEE()");
const blobfee = await ttest.testBLOBFEE();
console.log("Result:", blobfee.toString());
} catch (error) {
console.error("Error calling test:", error.message);
}
try {
console.log("Calling TTest.test()");
const tx = await ttest.test();
await tx.wait();
console.log("TTest.test() called successfully");
} catch (error) {
console.error("Error calling test:", error.message);
}
}
// Define the script
async function main() {
await test()
}
// Run the script
main().catch((error) => {
console.error("Error:", error);
});
\ No newline at end of file
const { ContractFactory } = require("ethers");
const hre = require("hardhat");
async function getTokenInfo(contract) {
const name = await contract.name();
console.log("Token Name:", name);
const symbol = await contract.symbol();
const totalSupply = await contract.totalSupply();
console.log("Token Symbol:", symbol);
console.log("Total Supply:", totalSupply.toString());
}
// Define the script
async function main() {
// Deploy the contract
const factory = await hre.ethers.getContractFactory(
"PublicToken"
);
// const contract = await factory.deploy({ value: hre.ethers.parseEther("0.0008") });
const contract = await factory.deploy();
await contract.waitForDeployment();
const depAddr = await contract.getAddress();
console.log("Contract deployed at:", depAddr);
await getTokenInfo(contract);
// const minttx = await contract.mint('0x88395111AB1586a4030dAC62a183542762929bbC', 10000);
// console.log("mint tx:", minttx.hash);
}
// Run the script
main().catch((error) => {
console.error("Error:", error);
});
\ No newline at end of file
const { ContractFactory } = require("ethers");
const hre = require("hardhat");
// Define the script
async function main() {
// Deploy the contract
const factory = await hre.ethers.getContractFactory(
"WMDD"
);
const contract = await factory.deploy();
await contract.waitForDeployment();
const depAddr = await contract.getAddress();
console.log("Contract deployed at:", depAddr);
}
// 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