From f466153651ac2534796f430572ae86b9b3bbdaeb Mon Sep 17 00:00:00 2001 From: aristotleee <148852099+aristotleee@users.noreply.github.com> Date: Tue, 5 Nov 2024 19:15:36 +0800 Subject: [PATCH 1/5] add test for ui script --- script/DeploymentsFile.sol | 6 ++- script/Test.s.sol | 2 +- script/TestForUI.s.sol | 79 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 script/TestForUI.s.sol diff --git a/script/DeploymentsFile.sol b/script/DeploymentsFile.sol index 4460f9e..6a895f9 100644 --- a/script/DeploymentsFile.sol +++ b/script/DeploymentsFile.sol @@ -10,12 +10,16 @@ contract DeploymentsFile is FileBase, StdChains { string public constant DEPLOYMENTS_PATH = "script/Deployments.json"; string private constant ROOT_KEY = "deployments"; - constructor() { + constructor(Chain[] memory customChains) { reload(DEPLOYMENTS_PATH); if (!valid) { vm.writeJson("{}", path); reload(DEPLOYMENTS_PATH); } + + for (uint256 i = 0; i < customChains.length; i++) { + setChain(customChains[i].chainAlias, customChains[i]); + } } function writeDeployment(address _storageAddress, address _bridgeAddress, address ebtcAddress) public { diff --git a/script/Test.s.sol b/script/Test.s.sol index 84111ed..35a994d 100644 --- a/script/Test.s.sol +++ b/script/Test.s.sol @@ -21,7 +21,7 @@ contract End2End is Script { if (!data.valid()) { revert("Invalid Data file"); } - deployments = new DeploymentsFile(); + deployments = new DeploymentsFile(new Chain[](0)); } function run() public { diff --git a/script/TestForUI.s.sol b/script/TestForUI.s.sol new file mode 100644 index 0000000..c0f244a --- /dev/null +++ b/script/TestForUI.s.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.26; + +import "forge-std/Script.sol"; +import "../test/mockup/EBTCTest.sol"; +import "../test/mockup/StorageTestnet.sol"; +import "../test/mockup/BridgeTestnet.sol"; +import {Util} from "../test/utils/Util.sol"; +import {TestData} from "../test/fixture/TestData.sol"; +import {DeploymentsFile} from "./DeploymentsFile.sol"; +import {StorageFixture, StorageSetupInfo} from "../test/fixture/StorageFixture.sol"; +import {Outpoint, ProofInfo} from "../src/interfaces/IBridge.sol"; +import "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; + +contract End2End is Script { + TestData data; + DeploymentsFile deployments; + Chain[] customChains; + + function setUp() public { + customChains.push( + Chain({name: "Anvil-For-UI", chainId: 831337, chainAlias: "anvil4ui", rpcUrl: "http://localhost:8545"}) + ); + for (uint256 i = 0; i < customChains.length; i++) { + setChain(customChains[i].chainAlias, customChains[i]); + } + + data = new TestData(); + if (!data.valid()) { + revert("Invalid Data file"); + } + + deployments = new DeploymentsFile(customChains); + } + + function run() public { + uint256 ownerPrivateKey = vm.envUint("PRIVATE_KEY_0"); + address withdrawer = vm.addr(vm.envUint("PRIVATE_KEY_1")); + StorageSetupInfo memory params = data._storage(data.pegOutStorageKey()); + + vm.startBroadcast(ownerPrivateKey); + IStorage _storage = _deployStorage(params); + EBTCTest ebtcTest = _deployEbtc(); + (IBridge bridge, EBTC ebtc) = _deployBridge(_storage, ebtcTest); + + _mintForTesting(withdrawer, ebtcTest); + vm.stopBroadcast(); + + deployments.writeDeployment(address(_storage), address(bridge), address(ebtc)); + } + + function _deployStorage(StorageSetupInfo memory params) public returns (IStorage _storage) { + _storage = new StorageTestnet( + params.step, + params.height, + IStorage.KeyBlock(params.blockHash, 0, params.timestamp), + IStorage.Epoch(bytes4(Endian.reverse32(params.bits)), params.epochTimestamp) + ); + _submit(_storage, params); + } + + function _deployBridge(IStorage _storage, EBTCTest ebtcTest) public returns (IBridge bridge, EBTC ebtc) { + ebtc = EBTC(ebtcTest); + bridge = new BridgeTestnet(ebtc, _storage, data.nOfNPubKey(), data.pegInTimelock()); + ebtc.setBridge(address(bridge)); + } + + function _deployEbtc() public returns (EBTCTest ebtcTest) { + ebtcTest = new EBTCTest(address(0)); + } + + function _mintForTesting(address receipient, EBTCTest ebtcTest) public { + ebtcTest.mintForTest(receipient, 100 ** ebtcTest.decimals()); + } + + function _submit(IStorage _storage, StorageSetupInfo memory params) public { + _storage.submit(params.headers, params.startHeight); + } +} From 0919cc3d55bea95c6577a5fcbe8f4139687dcf3a Mon Sep 17 00:00:00 2001 From: aristotleee <148852099+aristotleee@users.noreply.github.com> Date: Mon, 25 Nov 2024 18:12:12 +0800 Subject: [PATCH 2/5] update readme --- README.md | 18 ++++++++++++++++++ script/DeploymentsFile.sol | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0c8fdc5..3dcf56e 100644 --- a/README.md +++ b/README.md @@ -31,3 +31,21 @@ anvil -f https://mainnet.infura.io/v3/ --fork-block-number source .env forge script script/Test.s.sol:End2End --slow --sig "" --broadcast --rpc-url ${RPC_URL_ANVIL} ``` + +## Test for UI +1. Start Anvil forking mainnet, use anvil dev account private keys for PRIVATE_KEY_0 and PRIVATE_KEY_1 in .env + +2. run script TestForUI.s.sol to deploy bridge and eBtc
+*Try removing cache file if deployment fails* + +```bash +anvil --chain-id --state -f https://mainnet.infura.io/v3/ + +source .env +forge script script/TestForUI.s.sol:End2End --broadcast --rpc-url ${RPC_URL_ANVIL} +``` + +3. Use bridge and ebtc addresses from Deployments.json in UI +4. Add a custom network in wallet +5. Test on UI
+*May need 'Clear activity tab data' in settings/advaced to reset nonce* diff --git a/script/DeploymentsFile.sol b/script/DeploymentsFile.sol index 6a895f9..7c92f9f 100644 --- a/script/DeploymentsFile.sol +++ b/script/DeploymentsFile.sol @@ -24,7 +24,7 @@ contract DeploymentsFile is FileBase, StdChains { function writeDeployment(address _storageAddress, address _bridgeAddress, address ebtcAddress) public { string memory chain = getChain(block.chainid).name; - string memory timestamp = vm.toString(block.timestamp); + string memory timestamp = vm.toString(vm.unixTime()); string memory oldContent = content; loadOldContent(oldContent); From 2f32da83acf81c3ba55ecd2b12f0696c56cdbe7e Mon Sep 17 00:00:00 2001 From: aristotleee <148852099+aristotleee@users.noreply.github.com> Date: Mon, 9 Dec 2024 16:30:04 +0800 Subject: [PATCH 3/5] add burnEBTC test script for e2e --- data/fetchTestDataPegOut.mjs | 5 +- data/lib/helper.mjs | 8 ++- data/lib/provider.mjs | 6 ++ .../0.Deployment.s.sol} | 16 ++--- script/e2e/1.BurnEBTC.s.sol | 67 +++++++++++++++++++ src/Bridge.sol | 3 +- src/libraries/Coder.sol | 8 ++- test/fixture/TestData.sol | 2 +- 8 files changed, 99 insertions(+), 16 deletions(-) rename script/{TestForUI.s.sol => e2e/0.Deployment.s.sol} (84%) create mode 100644 script/e2e/1.BurnEBTC.s.sol diff --git a/data/fetchTestDataPegOut.mjs b/data/fetchTestDataPegOut.mjs index e35dc00..b67753a 100644 --- a/data/fetchTestDataPegOut.mjs +++ b/data/fetchTestDataPegOut.mjs @@ -6,7 +6,7 @@ import { getProvider } from './lib/provider.mjs' import { reverseBytesNArray, BLOCK_HEADER_BYTES, EPOCH_BLOCK_COUNT } from './lib/coder.mjs' import { getBlockInfoByHeight, getTransactionInfo } from './lib/api.mjs' - // usage: `node script/fetchTestDataPegOut.mjs ` + // usage: `node script/fetchTestDataPegOut.mjs ` ; (async () => { const providerId = parseInt(process.argv[2]) const provider = getProvider(providerId) @@ -57,7 +57,8 @@ import { getBlockInfoByHeight, getTransactionInfo } from './lib/api.mjs' testData.pegOut.withdrawer = SHARED_DATA.withdrawerEvmAddress testData.pegOut.pegOutTimestamp = SHARED_DATA.pegOutTimestamp - testData.pegOut.amount = SHARED_DATA.pegOutValue + // testData.pegOut.amount = SHARED_DATA.pegOutValue + testData.pegOut.amount = proofResult.proofInfo.vin[0].prevout.value testData.pegOut.withdrawerPubKey = SHARED_DATA.withdrawerPubKey testData.pegOut.operatorPubKey = SHARED_DATA.operatorPubKey testData.pegOut.nOfNPubKey = SHARED_DATA.nOfNPubKey diff --git a/data/lib/helper.mjs b/data/lib/helper.mjs index b865e9b..4dd6f37 100644 --- a/data/lib/helper.mjs +++ b/data/lib/helper.mjs @@ -12,12 +12,14 @@ const TEST_DATA_SAMPLE_FILE = path.join(__dirname, '../../test/fixture/test-data export const TEST_DATA_FILE = path.join(__dirname, '../../test/fixture/test-data.json') export const SHARED_DATA = { - depositorEvmAddress: '0xDDdDddDdDdddDDddDDddDDDDdDdDDdDDdDDDDDDd', + depositorEvmAddress: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', //anvil 1 pegInTimelock: 1, pegInValue: 131072, depositorPubKey: '0xedf074e2780407ed6ff9e291b8617ee4b4b8d7623e85b58318666f33a422301b', - withdrawerEvmAddress: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', - pegOutValue: 131072, + // withdrawerEvmAddress: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', + withdrawerEvmAddress: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', //anvil 1 + // withdrawerEvmAddress: '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC', //anvil 2 + pegOutValue: 131072, //TODO: remove this, should get it in prevout pegOutTimestamp: 1722328130, withdrawerPubKey: '0x02f80c9d1ef9ff640df2058c431c282299f48424480d34f1bade2274746fb4df8b', operatorPubKey: '0x03484db4a2950d63da8455a1b705b39715e4075dd33511d0c7e3ce308c93449deb', diff --git a/data/lib/provider.mjs b/data/lib/provider.mjs index fd85e4b..8749c64 100644 --- a/data/lib/provider.mjs +++ b/data/lib/provider.mjs @@ -12,6 +12,10 @@ const BLOCKSTREAM_BLOCK_CHUNK_SIZE = 10 const MUTINYNET_API_URL = 'https://www.mutinynet.com/api' const MUTINYNET_BLOCK_CHUNK_SIZE = 10 +// local regtest network +const LOCAL_REGTEST_API_URL = 'http://127.0.0.1:8094/regtest/api' +const LOCAL_REGTEST_BLOCK_CHUNK_SIZE = 10 + const getAPIs = (baseUrl, blockChunkSize) => { return { blockChunkSize, @@ -30,6 +34,8 @@ export const getProvider = (providerId) => { return getAPIs(BLOCKSTREAM_API_URL, BLOCKSTREAM_BLOCK_CHUNK_SIZE) case 2: return getAPIs(MUTINYNET_API_URL, MUTINYNET_BLOCK_CHUNK_SIZE) + case 3: + return getAPIs(LOCAL_REGTEST_API_URL, LOCAL_REGTEST_BLOCK_CHUNK_SIZE) default: throw new Error(`unknown provider id ${providerId}`) } diff --git a/script/TestForUI.s.sol b/script/e2e/0.Deployment.s.sol similarity index 84% rename from script/TestForUI.s.sol rename to script/e2e/0.Deployment.s.sol index c0f244a..2a6931e 100644 --- a/script/TestForUI.s.sol +++ b/script/e2e/0.Deployment.s.sol @@ -2,14 +2,14 @@ pragma solidity ^0.8.26; import "forge-std/Script.sol"; -import "../test/mockup/EBTCTest.sol"; -import "../test/mockup/StorageTestnet.sol"; -import "../test/mockup/BridgeTestnet.sol"; -import {Util} from "../test/utils/Util.sol"; -import {TestData} from "../test/fixture/TestData.sol"; -import {DeploymentsFile} from "./DeploymentsFile.sol"; -import {StorageFixture, StorageSetupInfo} from "../test/fixture/StorageFixture.sol"; -import {Outpoint, ProofInfo} from "../src/interfaces/IBridge.sol"; +import "../../test/mockup/EBTCTest.sol"; +import "../../test/mockup/StorageTestnet.sol"; +import "../../test/mockup/BridgeTestnet.sol"; +import {Util} from "../../test/utils/Util.sol"; +import {TestData} from "../../test/fixture/TestData.sol"; +import {DeploymentsFile} from "../DeploymentsFile.sol"; +import {StorageFixture, StorageSetupInfo} from "../../test/fixture/StorageFixture.sol"; +import {Outpoint, ProofInfo} from "../../src/interfaces/IBridge.sol"; import "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; contract End2End is Script { diff --git a/script/e2e/1.BurnEBTC.s.sol b/script/e2e/1.BurnEBTC.s.sol new file mode 100644 index 0000000..58c47c9 --- /dev/null +++ b/script/e2e/1.BurnEBTC.s.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.26; + +import "forge-std/Script.sol"; +import "../../test/mockup/EBTCTest.sol"; +import "../../test/mockup/StorageTestnet.sol"; +import "../../test/mockup/BridgeTestnet.sol"; +import {Util} from "../../test/utils/Util.sol"; +import {TestData} from "../../test/fixture/TestData.sol"; +import {DeploymentsFile} from "../DeploymentsFile.sol"; +import {StorageFixture, StorageSetupInfo} from "../../test/fixture/StorageFixture.sol"; +import {Outpoint, ProofInfo} from "../../src/interfaces/IBridge.sol"; +import "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; + +contract End2End is Script { + TestData data; + DeploymentsFile deployments; + Chain[] customChains; + + function setUp() public { + customChains.push( + Chain({name: "Anvil-For-UI", chainId: 831337, chainAlias: "anvil4ui", rpcUrl: "http://localhost:8545"}) + ); + for (uint256 i = 0; i < customChains.length; i++) { + setChain(customChains[i].chainAlias, customChains[i]); + } + + data = new TestData(); + if (!data.valid()) { + revert("Invalid Data file"); + } + + deployments = new DeploymentsFile(customChains); + } + + function run() public { + uint256 ownerPrivateKey = vm.envUint("PRIVATE_KEY_0"); + address withdrawer = vm.addr(vm.envUint("PRIVATE_KEY_1")); + StorageSetupInfo memory params = data._storage(data.pegOutStorageKey()); + (, address bridgeAddress, address ebtc) = deployments.getLastRunDeployment(); + ProofInfo memory proof = Util.paramToProof(data.proof(data.pegOutProofKey()), false); + + vm.startBroadcast(ownerPrivateKey); + // deploy new and reset storage for testing purpose + IStorage _storage = _deployStorage(params); + BridgeTestnet(bridgeAddress).setBlockStorage(_storage); + + IBridge(bridgeAddress).burnEBTC(withdrawer, proof); + vm.stopBroadcast(); + + deployments.writeDeployment(address(_storage), address(bridgeAddress), address(ebtc)); + } + + function _deployStorage(StorageSetupInfo memory params) public returns (IStorage _storage) { + _storage = new StorageTestnet( + params.step, + params.height, + IStorage.KeyBlock(params.blockHash, 0, params.timestamp), + IStorage.Epoch(bytes4(Endian.reverse32(params.bits)), params.epochTimestamp) + ); + _submit(_storage, params); + } + + function _submit(IStorage _storage, StorageSetupInfo memory params) public { + _storage.submit(params.headers, params.startHeight); + } +} diff --git a/src/Bridge.sol b/src/Bridge.sol index 851e611..fe4a9c5 100644 --- a/src/Bridge.sol +++ b/src/Bridge.sol @@ -38,6 +38,7 @@ contract Bridge is IBridge { bytes32 nOfNPubKey; bytes4 private version = 0x02000000; bytes4 private locktime = 0x00000000; + uint256 private constant DUST_AMOUNT = 10000; constructor(EBTC _ebtc, IStorage _blockStorage, bytes32 _nOfNPubKey) { ebtc = _ebtc; @@ -141,7 +142,7 @@ contract Bridge is IBridge { if (!txOut.scriptPubkeyWithoutLength().equals(inscriptionScript.generateP2WSHScriptPubKey())) { revert InvalidPegOutProofScriptPubKey(); } - if (txOut.value() != info.amount) { + if (txOut.value() != info.amount - DUST_AMOUNT) { revert InvalidPegOutProofAmount(); } bytes32 txId = ViewSPV.calculateTxId( diff --git a/src/libraries/Coder.sol b/src/libraries/Coder.sol index ede43e1..fdd7556 100644 --- a/src/libraries/Coder.sol +++ b/src/libraries/Coder.sol @@ -14,6 +14,7 @@ library Coder { uint256 public constant BLOCK_HEADER_LENGTH = 80; uint256 public constant MAX_TARGET = 0x00000000FFFF0000000000000000000000000000000000000000000000000000; uint256 public constant DIFFICULTY_PRECISION = 10 ** 6; + uint256 public constant DIFFICULTY_PRECISION_TESTNET = 10 ** 18; uint32 public constant EPOCH_BLOCK_COUNT = 2016; uint32 public constant EPOCH_TARGET_TIMESPAN = 10 * 60 * EPOCH_BLOCK_COUNT; @@ -54,7 +55,12 @@ library Coder { } function toDifficulty(uint256 target) internal pure returns (uint256) { - return MAX_TARGET * DIFFICULTY_PRECISION / target; + if (MAX_TARGET >= target) { + return MAX_TARGET * DIFFICULTY_PRECISION / target; + } else { + // for testnet to prevent overflow + return DIFFICULTY_PRECISION_TESTNET / (target / MAX_TARGET); + } } function bitToDifficulty(bytes32 bits) internal pure returns (uint256) { diff --git a/test/fixture/TestData.sol b/test/fixture/TestData.sol index 8400576..bfa9df5 100644 --- a/test/fixture/TestData.sol +++ b/test/fixture/TestData.sol @@ -65,7 +65,7 @@ contract TestData is FileBase { } function proof(string memory keyPrefix) public view validated returns (ProofParam memory) { - bytes memory merkleProof = abi.decode(node(string.concat(keyPrefix, ".merkleProof")), (bytes)); + bytes memory merkleProof = node(string.concat(keyPrefix, ".merkleProof")); bytes memory parents = abi.decode(node(string.concat(keyPrefix, ".parents")), (bytes)); bytes memory children = abi.decode(node(string.concat(keyPrefix, ".children")), (bytes)); bytes memory rawTx = abi.decode(node(string.concat(keyPrefix, ".rawTx")), (bytes)); From 7eb2d1ee6983ac5b9c51a4dd0978dc6a528462db Mon Sep 17 00:00:00 2001 From: aristotleee <148852099+aristotleee@users.noreply.github.com> Date: Mon, 9 Dec 2024 16:44:47 +0800 Subject: [PATCH 4/5] update readme --- README.md | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 3dcf56e..b8918d3 100644 --- a/README.md +++ b/README.md @@ -34,18 +34,33 @@ forge script script/Test.s.sol:End2End --slow --sig "" --broadcas ## Test for UI 1. Start Anvil forking mainnet, use anvil dev account private keys for PRIVATE_KEY_0 and PRIVATE_KEY_1 in .env - 2. run script TestForUI.s.sol to deploy bridge and eBtc
*Try removing cache file if deployment fails* - ```bash anvil --chain-id --state -f https://mainnet.infura.io/v3/ source .env -forge script script/TestForUI.s.sol:End2End --broadcast --rpc-url ${RPC_URL_ANVIL} +forge script script/e2e/0.Deployment.s.sol:End2End --broadcast --rpc-url ${RPC_URL_ANVIL} ``` - 3. Use bridge and ebtc addresses from Deployments.json in UI -4. Add a custom network in wallet -5. Test on UI
+4. Add anvil as custom network in wallet +5. *[In BitVM]* Run e2e test step 0 to create a completed peg in
+*Follow instructions in BitVM to run regtest environment* +```bash +cargo test -- --nocapture --ignored test_e2e_0 +``` +5. Test on UI to initiate a peg out
*May need 'Clear activity tab data' in settings/advaced to reset nonce* +6. *[In BitVM]* Run e2e test step 1 to broadcast peg out tx +```bash +cargo test -- --nocapture --ignored test_e2e_1 +``` +7. Fetch data from local regtest and burn eBTC +```bash +node script/fetchTestDataPegOut.mjs 3 +forge script script/e2e/1.BurnEBTC.s.sol:End2End --broadcast --rpc-url ${RPC_URL_ANVIL} +``` +8. *[In BitVM]* Run e2e test step 2 to verify burn events +```bash +cargo test -- --nocapture --ignored test_e2e_2 +``` From af3e17c49de693fe19ee4612285fb3c7189f099b Mon Sep 17 00:00:00 2001 From: aristotleee <148852099+aristotleee@users.noreply.github.com> Date: Wed, 11 Dec 2024 14:47:27 +0800 Subject: [PATCH 5/5] update readme --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b8918d3..edbd0ea 100644 --- a/README.md +++ b/README.md @@ -34,10 +34,10 @@ forge script script/Test.s.sol:End2End --slow --sig "" --broadcas ## Test for UI 1. Start Anvil forking mainnet, use anvil dev account private keys for PRIVATE_KEY_0 and PRIVATE_KEY_1 in .env -2. run script TestForUI.s.sol to deploy bridge and eBtc
+2. run deployment script to deploy bridge and eBtc
*Try removing cache file if deployment fails* ```bash -anvil --chain-id --state -f https://mainnet.infura.io/v3/ +anvil --chain-id 831337 --state -f https://mainnet.infura.io/v3/ source .env forge script script/e2e/0.Deployment.s.sol:End2End --broadcast --rpc-url ${RPC_URL_ANVIL} @@ -55,7 +55,7 @@ cargo test -- --nocapture --ignored test_e2e_0 ```bash cargo test -- --nocapture --ignored test_e2e_1 ``` -7. Fetch data from local regtest and burn eBTC +7. Fetch data from local regtest and reset storage ```bash node script/fetchTestDataPegOut.mjs 3 forge script script/e2e/1.BurnEBTC.s.sol:End2End --broadcast --rpc-url ${RPC_URL_ANVIL}