Commit e7dfd829 authored by Developer's avatar Developer

update revert contract

parent 4c71963d
// filepath: /Users/luxq/work/wuban/testcontract/contracts/Distributor.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/// @dev Minimal ERC20 interface for token transfers
interface IERC20 {
function transfer(address to, uint256 amount) external returns (bool);
function transferFrom(address from, address to, uint256 amount) external returns (bool);
function balanceOf(address account) external view returns (uint256);
}
/// @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;
}
/// @notice Distribute ERC20 tokens equally to all recipients from caller's balance.
/// @dev Caller must approve this contract to spend at least totalAmount tokens before calling.
/// This will attempt RECIPIENT_COUNT transfers in a single transaction and may run out of gas.
/// @param token The ERC20 token contract address
/// @param totalAmount The total amount of tokens to distribute
function distributeERC20All(address token, uint256 totalAmount) external {
require(totalAmount > 0, "No tokens provided");
uint256 per = totalAmount / RECIPIENT_COUNT;
require(per > 0, "Amount too small for equal distribution");
IERC20 erc20 = IERC20(token);
for (uint256 i = 0; i < RECIPIENT_COUNT; ++i) {
// transfer from caller to recipient
bool success = erc20.transferFrom(msg.sender, recipients[i], per);
if (!success) {
// continue on failure to maximize distribution
}
}
}
/// @notice Distribute ERC20 tokens to recipients in the half-open range [start, end)
/// @dev Caller must approve this contract to spend the required tokens before calling.
/// @param token The ERC20 token contract address
/// @param start The starting index (inclusive)
/// @param end The end index (exclusive)
/// @param amountPer Amount of tokens to send to each recipient in the range
function distributeERC20Range(address token, uint256 start, uint256 end, uint256 amountPer) external {
require(start < end && end <= RECIPIENT_COUNT, "Invalid range");
require(amountPer > 0, "Amount must be greater than 0");
IERC20 erc20 = IERC20(token);
for (uint256 i = start; i < end; ++i) {
// transfer from caller to recipient
bool success = erc20.transferFrom(msg.sender, recipients[i], amountPer);
if (!success) {
// continue on failure to maximize distribution
}
}
}
// Allow the contract to receive ETH
receive() external payable {}
}
...@@ -25,5 +25,72 @@ contract RevertContract { ...@@ -25,5 +25,72 @@ contract RevertContract {
require(success, "Call failed"); require(success, "Call failed");
return success; return success;
} }
// --- New helper functions that call another contract which may revert ---
// Call a target's willRevertRequire() and let a revert bubble up (will revert this call too)
function callTargetAndBubble(address payable _target) public payable {
// typed call to external contract - if target reverts, this call will revert (bubble)
emit log("callTargetAndBubble: calling target, will bubble if it reverts");
RevertTarget(_target).willRevertRequire{value: msg.value}();
emit log("callTargetAndBubble: target call succeeded, this will be reached only if no revert");
}
// Call a target with try/catch and handle the revert without bubbling
function callTargetTryCatch(address payable _target) public payable returns (bool) {
emit log("callTargetTryCatch: calling target with try/catch, will handle revert");
try RevertTarget(_target).willRevertRequire{value: msg.value}() {
emit log("callTargetTryCatch: success");
return true;
} catch Error(string memory reason) {
// This is executed in case of revert with a reason string
emit log(reason);
return false;
} catch (bytes memory /*lowLevelData*/) {
// This is executed in case of a revert without a reason (or other failures)
emit log("callTargetTryCatch: unknown revert");
return false;
}
}
// Low-level call example: capture success flag and do not revert
function callTargetLowLevel(address payable _target) public payable returns (bool success, bytes memory data) {
emit log("callTargetLowLevel: calling target with low-level call, will handle success/failure");
(success, data) = _target.call{value: msg.value}(abi.encodeWithSignature("willRevertRequire()"));
if (!success) {
emit log("callTargetLowLevel: call failed, handled");
} else {
emit log("callTargetLowLevel: call succeeded");
}
}
receive() external payable {}
}
// Second-level contract that intentionally reverts in different ways
contract RevertTarget {
event logtarget(string message);
// revert via require (provides a reason string)
function willRevertRequire() external payable {
emit logtarget("RevertTarget: willRevertRequire called, about to revert");
require(false, "RevertTarget: require failed");
emit logtarget("RevertTarget: willRevertRequire - this will never be reached");
}
// explicit revert with message
function willRevertExplicit() external payable {
emit logtarget("RevertTarget: willRevertExplicit called, about to revert");
revert("RevertTarget: explicit revert");
emit logtarget("RevertTarget: willRevertExplicit - this will never be reached");
}
// panic via assert (produces a Panic(uint256))
function willPanic() external payable {
emit logtarget("RevertTarget: willPanic called, about to panic");
assert(false);
emit logtarget("RevertTarget: willPanic - this will never be reached");
}
// accept ether so tests can send value
receive() external payable {} receive() external payable {}
} }
\ No newline at end of file
// javascript
// scripts/deploy_distribute.js
// Deploy Distributor and call distributeAll to send ETH to all generated recipients
const hre = require("hardhat");
const { ethers } = hre;
async function main() {
const [deployer] = await ethers.getSigners();
console.log("Using deployer:", deployer.address);
const Distributor = await ethers.getContractFactory("Distributor");
// Use a simple compatible bytes32 seed: keccak256("seed123") via ethers.utils.id
const seed = "0x626c756500000000000000000000000000000000000000000000000000000000";
// print current block number.
const blockNumber = await ethers.provider.getBlockNumber();
console.log("Current block number:", blockNumber);
// get balance for deployer
const deployerBalance = await ethers.provider.getBalance(deployer.address);
console.log("Deployer balance:", ethers.formatEther(deployerBalance), "ETH");
console.log("Deploying Distributor...");
const distributor = await Distributor.deploy(seed);
await distributor.waitForDeployment(); // <-- use waitForDeployment() in ethers v6
const contractAddress = await distributor.getAddress();
console.log("Distributor deployed to:", contractAddress);
// fetch recipients (beware: large array)
let recipients = [];
try {
recipients = await distributor.getRecipients();
} catch (err) {
// some networks may limit returning large arrays; fall back to calling recipientsCount and reading a few
const count = await distributor.recipientsCount();
for (let i = 0; i < Math.min(5, count); i++) {
const r = await distributor.recipients(i);
recipients.push(r);
}
}
console.log("Recipients generated (cached):", recipients.length);
console.log("First 5 recipients:");
for (let i = 0; i < Math.min(5, recipients.length); i++) {
console.log(i, recipients[i]);
}
// show contract balance before
const provider = ethers.provider;
let bal = await provider.getBalance(contractAddress);
console.log("Contract balance before:", ethers.formatEther(bal));
// Send 1 ETH to distribute among 700 recipients
const amountToSend = ethers.parseEther("1.0");
console.log(`Calling distributeAll with ${ethers.formatEther(amountToSend)} ETH...`);
const tx = await distributor.connect(deployer).distributeAll({ value: amountToSend, gasLimit: 82_000_000 });
const receipt = await tx.wait();
console.log("distributeAll tx hash:", receipt);
// show contract balance after
bal = await provider.getBalance(contractAddress);
console.log("Contract balance after:", ethers.formatEther(bal));
// Show balances of first 5 recipients
console.log("Balances of first 5 recipients after distribution:");
for (let i = 0; i < Math.min(5, recipients.length); i++) {
const b = await provider.getBalance(recipients[i]);
console.log(i, recipients[i], ethers.formatEther(b));
}
console.log("Done.");
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
// filepath: /Users/luxq/work/wuban/testcontract/scripts/distribute_erc20_all.js
// Deploy Distributor and PublicToken, approve Distributor to spend tokens, and call distributeERC20All
const hre = require("hardhat");
const { ethers } = hre;
async function main() {
const [deployer] = await ethers.getSigners();
console.log("Using deployer:", deployer.address);
// Deploy Distributor (same pattern as deploy_distribute.js)
const Distributor = await ethers.getContractFactory("Distributor");
const seed = "0x626c756500000000000000000000000000000000000000000000000000000000";
console.log("Deploying Distributor...");
const distributor = await Distributor.deploy(seed);
await distributor.waitForDeployment();
const distributorAddr = await distributor.getAddress();
console.log("Distributor deployed to:", distributorAddr);
// Deploy PublicToken
const Token = await ethers.getContractFactory("PublicToken");
console.log("Deploying PublicToken...");
const token = await Token.deploy();
await token.waitForDeployment();
const tokenAddr = await token.getAddress();
console.log("PublicToken deployed to:", tokenAddr);
// Determine decimals and prepare totalAmount to distribute
const decimals = await token.decimals();
// choose an amount to distribute: 1000 tokens (in token units)
const totalAmount = ethers.parseUnits("1000", decimals);
console.log(`Will distribute totalAmount = ${totalAmount.toString()} (parsed with decimals = ${decimals})`);
// Mint tokens to deployer so there are tokens to distribute
console.log("Minting tokens to deployer...");
const mintTx = await token.mint(deployer.address, totalAmount);
console.log("Mint tx:", mintTx.hash);
await mintTx.wait();
// Approve Distributor to spend tokens on behalf of deployer
console.log("Approving distributor to spend tokens...");
const approveTx = await token.approve(distributorAddr, totalAmount);
console.log("Approve tx:", approveTx.hash);
await approveTx.wait();
// Fetch recipients (try full array, fallback to reading a few indices)
let recipients = [];
try {
recipients = await distributor.getRecipients();
} catch (err) {
console.log("getRecipients() failed, falling back to reading indices:", err.message || err);
const count = await distributor.recipientsCount();
for (let i = 0; i < Math.min(5, count); i++) {
const r = await distributor.recipients(i);
recipients.push(r);
}
}
console.log("Recipients count (cached):", recipients.length);
console.log("First 5 recipients:");
for (let i = 0; i < Math.min(5, recipients.length); i++) {
console.log(i, recipients[i]);
}
// Call distributeERC20All
console.log("Calling distributeERC20All...");
const tx = await distributor.connect(deployer).distributeERC20All(tokenAddr, totalAmount, { gasLimit: 82_000_000 });
const receipt = await tx.wait();
console.log("distributeERC20All tx receipt:", receipt.transactionHash);
// Compute per-recipient amount and show balances for first 5 recipients
const RECIPIENT_COUNT = await distributor.recipientsCount();
const per = totalAmount / RECIPIENT_COUNT;
console.log(`Per recipient amount (integer division): ${per.toString()}`);
console.log("Token balances of first 5 recipients:");
for (let i = 0; i < Math.min(5, recipients.length); i++) {
const b = await token.balanceOf(recipients[i]);
console.log(i, recipients[i], b.toString());
}
console.log("Done.");
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
const { ContractFactory } = require("ethers");
const hre = require("hardhat"); const hre = require("hardhat");
async function getTokenInfo(contract) { async function getTokenInfo(contract) {
...@@ -11,26 +10,106 @@ async function getTokenInfo(contract) { ...@@ -11,26 +10,106 @@ async function getTokenInfo(contract) {
console.log("Total Supply:", totalSupply.toString()); console.log("Total Supply:", totalSupply.toString());
} }
// Define the script // Helper: fetch and print recent Transfer and Approval events for a contract
async function main() { async function getLogs(contract, lookbackBlocks = 1000) {
const provider = hre.ethers.provider;
const latest = await provider.getBlockNumber();
const fromBlock = Math.max(0, latest - lookbackBlocks);
console.log(`Fetching logs from block ${fromBlock} to ${latest}...`);
const transferFilter = contract.filters.Transfer();
const approvalFilter = contract.filters.Approval();
const transferEvents = await contract.queryFilter(transferFilter, fromBlock, latest);
const approvalEvents = await contract.queryFilter(approvalFilter, fromBlock, latest);
const printEvent = (e) => {
const args = e.args ? Array.from(e.args).map(a => (a && a.toString ? a.toString() : a)) : [];
return `[block:${e.blockNumber} tx:${e.transactionHash}] ${e.event}(${args.join(', ')})`;
};
console.log(`Found ${transferEvents.length} Transfer event(s):`);
transferEvents.forEach(e => console.log(printEvent(e)));
console.log(`Found ${approvalEvents.length} Approval event(s):`);
approvalEvents.forEach(e => console.log(printEvent(e)));
}
// Small helper to print an address balance
async function printBalance(contract, address, label) {
const bal = await contract.balanceOf(address);
console.log(`${label} balance (${address}):`, bal.toString());
}
// Deploy the contract // Main script
const factory = await hre.ethers.getContractFactory( async function main() {
"PublicToken" // Deploy the PublicToken contract
); const factory = await hre.ethers.getContractFactory("PublicToken");
// const contract = await factory.deploy({ value: hre.ethers.parseEther("0.0008") });
const contract = await factory.deploy(); const contract = await factory.deploy();
await contract.waitForDeployment(); await contract.waitForDeployment();
const depAddr = await contract.getAddress(); const depAddr = await contract.getAddress();
console.log("Contract deployed at:", depAddr); console.log("Contract deployed at:", depAddr);
// Basic info
await getTokenInfo(contract); await getTokenInfo(contract);
// const minttx = await contract.mint('0x88395111AB1586a4030dAC62a183542762929bbC', 10000);
// console.log("mint tx:", minttx.hash);
// Show any existing logs (if any)
await getLogs(contract);
// Mint tokens and wait for mining so logs are available
const recipient = '0x88395111AB1586a4030dAC62a183542762929bbC';
const amount = 10000;
const minttx = await contract.mint(recipient, amount);
console.log("mint to user:",recipient, "tx:", minttx.hash);
await minttx.wait();
// Fetch logs again to show the mint Transfer event
await getLogs(contract);
const signers = await hre.ethers.getSigners();
// Define a spender (use signer[1] if present) and a transfer recipient (signer[2] or a fixed address)
const deployer = signers[0];
const spender = '0x1000000000000000000000000000000000000001';
const transferTo = '0x2000000000000000000000000000000000000002';
const approvalAmount = 5000;
try {
const approveTx = await contract.approve(spender, approvalAmount);
console.log('approve tx:', approveTx.hash);
await approveTx.wait();
} catch (err) {
console.log('approve failed:', err.message || err);
}
// Print allowance and balances
try {
const allowance = await contract.allowance(deployer, spender);
console.log(`Allowance of spender ${spender} by owner :`, deployer, allowance.toString());
} catch (err) {
console.log('Could not fetch allowance:', err.message || err);
}
// Now perform the transfer from deployer
const transferAmount = 300;
try {
const transferTx = await contract.transfer(transferTo, transferAmount);
console.log('transfer tx:', transferTx.hash);
await transferTx.wait();
} catch (err) {
console.log('transfer failed:', err.message || err);
}
// Print balances after operations
// await printBalance(contract, recipient, 'Recipient (final)');
// await printBalance(contract, transferTo, 'TransferTo (final)');
// Fetch logs to show the approve and transfer events
await getLogs(contract);
} }
// Run the script // Run
main().catch((error) => { main().catch((error) => {
console.error("Error:", error); console.error("Error:", error);
process.exitCode = 1;
}); });
\ 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