From bcf5a3d71cac27bf98bd2f217224db4a70589138 Mon Sep 17 00:00:00 2001 From: drewstone Date: Mon, 10 Jul 2023 07:26:34 -0600 Subject: [PATCH] Add proposals packages and remove dependence on sdk-core (#344) --- .gitignore | 3 + package.json | 2 +- packages/anchors/package.json | 1 - packages/anchors/src/Common.ts | 10 +- packages/anchors/src/VAnchor.ts | 13 +- packages/anchors/src/VAnchorForest.ts | 23 +- packages/anchors/src/types.ts | 2 +- .../contracts/test/FungibleTokenWrapper.t.sol | 17 + .../contracts/test/TokenWrapperHandler.t.sol | 6 + .../contracts/test/TreasuryHandler.t.sol | 6 + .../contracts/tokens/AaveTokenWrapper.sol | 4 +- .../contracts/tokens/FungibleTokenWrapper.sol | 14 +- .../contracts/tokens/TokenWrapper.sol | 13 +- .../test/governance/governable.test.ts | 2 +- .../contracts/test/trees/MerkleForest.test.ts | 2 +- .../test/trees/MerkleTreePoseidon.test.ts | 2 +- .../test/vanchor/ChainalysisVAnchor.test.ts | 4 +- .../test/vanchor/RateLimitedVAnchor.test.ts | 8 +- .../test/vanchor/mocks/SetupTxVAnchorMock.ts | 131 --- .../contracts/test/vanchor/vanchor.test.ts | 139 +--- .../test/vanchor/vanchorForest.test.ts | 54 +- .../test/vbridge/rescueERC20.test.ts | 4 +- .../test/vbridge/rescueNative.test.ts | 4 +- .../test/vbridge/signatureVBridge.test.ts | 72 +- .../test/vbridge/signatureVBridgeSide.test.ts | 4 +- packages/evm-test-utils/package.json | 1 - packages/evm-test-utils/src/localEvmChain.ts | 8 +- packages/interfaces/package.json | 2 +- packages/interfaces/src/IVAnchor.ts | 2 +- packages/interfaces/src/vanchor/index.ts | 2 +- packages/proposals/LICENSE.Apache-2.0 | 201 +++++ packages/proposals/LICENSE.MIT | 20 + packages/proposals/README.md | 16 + packages/proposals/package.json | 25 + packages/proposals/src/ProposalHeader.ts | 78 ++ packages/proposals/src/ProposalKinds.ts | 745 ++++++++++++++++++ packages/proposals/src/ResourceId.ts | 124 +++ .../proposals/src/__test__/proposals.spec.ts | 382 +++++++++ packages/proposals/src/index.ts | 27 + packages/proposals/tsconfig.build.json | 12 + packages/proposals/tsconfig.json | 3 + packages/tokens/package.json | 1 - packages/tokens/src/FungibleTokenWrapper.ts | 2 +- packages/tokens/src/Treasury.ts | 2 +- packages/utils/.mocharc.json | 5 + packages/utils/package.json | 14 +- packages/utils/src/__test__/keypair.spec.ts | 50 ++ .../utils/src/__test__/merkle-tree.spec.ts | 124 +++ packages/utils/src/__test__/note.spec.ts | 521 ++++++++++++ .../utils/src/__test__/typed-chain-id.spec.ts | 66 ++ packages/utils/src/__test__/utxo.spec.ts | 197 +++++ packages/utils/src/{ => bytes}/hexToU8a.ts | 0 packages/utils/src/bytes/index.ts | 2 + packages/utils/src/{ => bytes}/u8aToHex.ts | 0 packages/utils/src/fixtures.ts | 8 +- packages/utils/src/index.ts | 6 +- .../utils/src/proof/build-variable-witness.ts | 341 ++++++++ packages/utils/src/proof/index.ts | 87 ++ packages/utils/src/proof/variable-anchor.ts | 84 ++ packages/utils/src/protocol/index.ts | 15 + packages/utils/src/protocol/keypair.ts | 189 +++++ packages/utils/src/protocol/merkle-tree.ts | 327 ++++++++ packages/utils/src/protocol/note.ts | 507 ++++++++++++ packages/utils/src/protocol/typed-chain-id.ts | 99 +++ packages/utils/src/protocol/utxo.ts | 297 +++++++ packages/utils/src/types.ts | 12 - packages/utils/src/utils.ts | 148 ++-- packages/utils/tsconfig.build.json | 2 +- packages/vbridge/package.json | 1 - packages/vbridge/src/SignatureBridgeSide.ts | 2 +- packages/vbridge/src/VBridge.ts | 4 +- scripts/evm/bridgeActions/changeGovernor.ts | 14 - .../evm/bridgeActions/transactWrapNative.ts | 65 -- scripts/evm/deployments/LocalEvmVBridge.ts | 455 ----------- scripts/evm/deployments/VBridge8Side.ts | 190 ----- .../evm/deployments/create2/create2Bridge.ts | 336 -------- .../deployments/create2/deploymentScript.ts | 171 ---- .../create2/relayer-config/goerli.toml | 44 -- .../create2/relayer-config/moonbase.toml | 44 -- .../create2/relayer-config/optimism.toml | 44 -- .../create2/relayer-config/sepolia.toml | 44 -- scripts/evm/deployments/endPoints.ts | 35 - .../deployments/relayer-config/moonbase.toml | 40 - .../deployments/relayer-config/mumbai.toml | 40 - scripts/evm/deployments/utils.ts | 168 ---- scripts/evm/ethersGovernorWallets.ts | 64 -- scripts/evm/playground.ts | 37 - scripts/evm/tokens/approveTokenSpend.ts | 23 - scripts/evm/tokens/getTokenAllowance.ts | 21 - scripts/evm/tokens/getTokenBalance.ts | 18 - scripts/evm/tokens/viewTokensInWrapper.ts | 20 - .../evm/viewActions/viewRootAcrossBridge.ts | 13 - tsconfig.build.json | 7 +- tsconfig.json | 7 + yarn.lock | 27 +- 95 files changed, 4852 insertions(+), 2376 deletions(-) create mode 100644 packages/contracts/contracts/test/TokenWrapperHandler.t.sol create mode 100644 packages/contracts/contracts/test/TreasuryHandler.t.sol delete mode 100644 packages/contracts/test/vanchor/mocks/SetupTxVAnchorMock.ts create mode 100644 packages/proposals/LICENSE.Apache-2.0 create mode 100644 packages/proposals/LICENSE.MIT create mode 100644 packages/proposals/README.md create mode 100644 packages/proposals/package.json create mode 100644 packages/proposals/src/ProposalHeader.ts create mode 100644 packages/proposals/src/ProposalKinds.ts create mode 100644 packages/proposals/src/ResourceId.ts create mode 100644 packages/proposals/src/__test__/proposals.spec.ts create mode 100644 packages/proposals/src/index.ts create mode 100644 packages/proposals/tsconfig.build.json create mode 100644 packages/proposals/tsconfig.json create mode 100644 packages/utils/.mocharc.json create mode 100644 packages/utils/src/__test__/keypair.spec.ts create mode 100644 packages/utils/src/__test__/merkle-tree.spec.ts create mode 100644 packages/utils/src/__test__/note.spec.ts create mode 100644 packages/utils/src/__test__/typed-chain-id.spec.ts create mode 100644 packages/utils/src/__test__/utxo.spec.ts rename packages/utils/src/{ => bytes}/hexToU8a.ts (100%) create mode 100644 packages/utils/src/bytes/index.ts rename packages/utils/src/{ => bytes}/u8aToHex.ts (100%) create mode 100644 packages/utils/src/proof/build-variable-witness.ts create mode 100644 packages/utils/src/proof/index.ts create mode 100644 packages/utils/src/proof/variable-anchor.ts create mode 100644 packages/utils/src/protocol/index.ts create mode 100644 packages/utils/src/protocol/keypair.ts create mode 100644 packages/utils/src/protocol/merkle-tree.ts create mode 100644 packages/utils/src/protocol/note.ts create mode 100644 packages/utils/src/protocol/typed-chain-id.ts create mode 100644 packages/utils/src/protocol/utxo.ts delete mode 100644 packages/utils/src/types.ts delete mode 100644 scripts/evm/bridgeActions/changeGovernor.ts delete mode 100644 scripts/evm/bridgeActions/transactWrapNative.ts delete mode 100755 scripts/evm/deployments/LocalEvmVBridge.ts delete mode 100644 scripts/evm/deployments/VBridge8Side.ts delete mode 100644 scripts/evm/deployments/create2/create2Bridge.ts delete mode 100644 scripts/evm/deployments/create2/deploymentScript.ts delete mode 100644 scripts/evm/deployments/create2/relayer-config/goerli.toml delete mode 100644 scripts/evm/deployments/create2/relayer-config/moonbase.toml delete mode 100644 scripts/evm/deployments/create2/relayer-config/optimism.toml delete mode 100644 scripts/evm/deployments/create2/relayer-config/sepolia.toml delete mode 100644 scripts/evm/deployments/endPoints.ts delete mode 100644 scripts/evm/deployments/relayer-config/moonbase.toml delete mode 100644 scripts/evm/deployments/relayer-config/mumbai.toml delete mode 100644 scripts/evm/deployments/utils.ts delete mode 100644 scripts/evm/ethersGovernorWallets.ts delete mode 100644 scripts/evm/playground.ts delete mode 100644 scripts/evm/tokens/approveTokenSpend.ts delete mode 100644 scripts/evm/tokens/getTokenAllowance.ts delete mode 100644 scripts/evm/tokens/getTokenBalance.ts delete mode 100644 scripts/evm/tokens/viewTokensInWrapper.ts delete mode 100644 scripts/evm/viewActions/viewRootAcrossBridge.ts diff --git a/.gitignore b/.gitignore index f9fa28053..64ec54189 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,6 @@ yarn.lock broadcast/* broadcast/*/31337/ .direnv + +coverage +report diff --git a/package.json b/package.json index add035497..f76a14cae 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,6 @@ "@typechain/hardhat": "^6.1.5", "@types/chai": "^4.3.4", "@types/mocha": "^10.0.1", - "@webb-tools/sdk-core": "0.1.4-126", "@webb-tools/test-utils": "0.1.4-126", "chai": "^4.3.7", "circomlibjs": "^0.0.8", @@ -62,6 +61,7 @@ "hardhat-preprocessor": "^0.1.5", "itertools": "^2.1.1", "lerna": "^6.6.1", + "mocha": "^10.2.0", "nx": "^15.9.2", "prettier-plugin-solidity": "^1.1.3", "rimraf": "^5.0.0", diff --git a/packages/anchors/package.json b/packages/anchors/package.json index ef1c57072..02ac5a86c 100644 --- a/packages/anchors/package.json +++ b/packages/anchors/package.json @@ -14,7 +14,6 @@ "@webb-tools/contracts": "^1.0.4", "@webb-tools/create2-utils": "^1.0.4", "@webb-tools/interfaces": "^1.0.4", - "@webb-tools/sdk-core": "0.1.4-126", "@webb-tools/tokens": "^1.0.4", "@webb-tools/utils": "^1.0.4", "circomlibjs": "^0.0.8", diff --git a/packages/anchors/src/Common.ts b/packages/anchors/src/Common.ts index 6cf627040..616eb2a3e 100644 --- a/packages/anchors/src/Common.ts +++ b/packages/anchors/src/Common.ts @@ -12,10 +12,8 @@ import { Utxo, randomBN, UtxoGenInput, - CircomUtxo, MerkleTree, - getVAnchorExtDataHash, -} from '@webb-tools/sdk-core'; +} from '@webb-tools/utils'; import { hexToU8a, getChainIdType, ZERO_BYTES32, FIELD_SIZE } from '@webb-tools/utils'; import { checkNativeAddress, splitTransactionOptions } from './utils'; import { @@ -47,8 +45,8 @@ export abstract class WebbBridge { this.latestSyncedBlock = 0; } - public static async generateUTXO(input: UtxoGenInput): Promise { - return CircomUtxo.generateUtxo(input); + public static generateUTXO(input: UtxoGenInput): Utxo { + return Utxo.generateUtxo(input); } public static createRootsBytes(rootArray: string[]) { @@ -240,7 +238,7 @@ export abstract class WebbBridge { while (utxos.length !== 2 && utxos.length < maxLen) { utxos.push( - await CircomUtxo.generateUtxo({ + Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: chainId.toString(), diff --git a/packages/anchors/src/VAnchor.ts b/packages/anchors/src/VAnchor.ts index 1abe772e6..8f4e2881c 100644 --- a/packages/anchors/src/VAnchor.ts +++ b/packages/anchors/src/VAnchor.ts @@ -12,19 +12,15 @@ import { IVariableAnchorPublicInputs, } from '@webb-tools/interfaces'; import { - CircomProvingManager, - CircomUtxo, + Utxo, FIELD_SIZE, Keypair, - LeafIdentifier, MerkleProof, MerkleTree, - ProvingManagerSetupInput, - Utxo, buildVariableWitnessCalculator, generateVariableWitnessInput, toFixedHex, -} from '@webb-tools/sdk-core'; +} from '@webb-tools/utils'; import { Proof, VAnchorProofInputs, @@ -58,7 +54,6 @@ export class VAnchor extends WebbBridge implements IVAnchor implements IVAnchor { try { - const decryptedUtxo = await CircomUtxo.decrypt(owner, enc); + const decryptedUtxo = Utxo.decrypt(owner, enc); // In order to properly calculate the nullifier, an index is required. // The decrypt function generates a utxo without an index, and the index is a readonly property. // So, regenerate the utxo with the proper index. - const regeneratedUtxo = await CircomUtxo.generateUtxo({ + const regeneratedUtxo = Utxo.generateUtxo({ amount: decryptedUtxo.amount, backend: 'Circom', blinding: hexToU8a(decryptedUtxo.blinding), diff --git a/packages/anchors/src/VAnchorForest.ts b/packages/anchors/src/VAnchorForest.ts index e0579682a..8a8876199 100644 --- a/packages/anchors/src/VAnchorForest.ts +++ b/packages/anchors/src/VAnchorForest.ts @@ -4,14 +4,7 @@ import { VAnchorForest as VAnchorForestContract, VAnchorForest__factory, } from '@webb-tools/contracts'; -import { - CircomProvingManager, - LeafIdentifier, - MerkleTree, - Utxo, - generateVariableWitnessInput, - toFixedHex, -} from '@webb-tools/sdk-core'; +import { MerkleTree, Utxo, generateVariableWitnessInput, toFixedHex } from '@webb-tools/utils'; import { poseidon_gencontract as poseidonContract } from 'circomlibjs'; import { BigNumber, BigNumberish, PayableOverrides, ethers } from 'ethers'; import { IVariableAnchorPublicInputs } from '@webb-tools/interfaces'; @@ -40,7 +33,6 @@ export class VAnchorForest extends WebbBridge { largeCircuitZkComponents: ZkComponents; token?: string; - provingManager: CircomProvingManager; gasBenchmark = []; proofTimeBenchmark = []; @@ -510,19 +502,6 @@ export class VAnchorForest extends WebbBridge { const chainId = getChainIdType(await this.signer.getChainId()); let extAmount = this.getExtAmount(inputs, outputs, fee); - // calculate the sum of input notes (for calculating the public amount) - let sumInputUtxosAmount: BigNumberish = 0; - - // Pass the identifier for leaves alongside the proof input - let leafIds: LeafIdentifier[] = []; - - for (const inputUtxo of inputs) { - sumInputUtxosAmount = BigNumber.from(sumInputUtxosAmount).add(inputUtxo.amount); - leafIds.push({ - index: inputUtxo.index!, // TODO: remove non-null assertion here - typedChainId: Number(inputUtxo.originChainId), - }); - } const { extData, extDataHash } = await this.generateExtData( recipient, BigNumber.from(extAmount), diff --git a/packages/anchors/src/types.ts b/packages/anchors/src/types.ts index 51bb46ff4..2247ee79b 100644 --- a/packages/anchors/src/types.ts +++ b/packages/anchors/src/types.ts @@ -2,7 +2,7 @@ // File contains all the types used in the anchors package import { IVariableAnchorExtData, IVariableAnchorPublicInputs } from '@webb-tools/interfaces'; -import { Keypair } from '@webb-tools/sdk-core'; +import { Keypair } from '@webb-tools/utils'; import { BigNumber, Overrides } from 'ethers'; export interface SetupTransactionResult { diff --git a/packages/contracts/contracts/test/FungibleTokenWrapper.t.sol b/packages/contracts/contracts/test/FungibleTokenWrapper.t.sol index 3cc5a2354..c8ec6bf04 100644 --- a/packages/contracts/contracts/test/FungibleTokenWrapper.t.sol +++ b/packages/contracts/contracts/test/FungibleTokenWrapper.t.sol @@ -35,6 +35,23 @@ contract FungibleTokenWrapperTest is PRBTest, StdCheats { token.initialize(feePercentage, feeRecipient, handler, limit, isNativeAllowed, admin); } + function test_initialize() public { + FungibleTokenWrapper new_token = new FungibleTokenWrapper("TOKEN", "TKN"); + uint16 feePercentage = 0; + address feeRecipient = alice; + address handler = address(tokenHandler); + uint256 limit = 100 ether; + bool isNativeAllowed = true; + address admin = alice; + new_token.initialize(feePercentage, feeRecipient, handler, limit, isNativeAllowed, admin); + } + + function test_shouldFailIfUninitialized() public { + FungibleTokenWrapper new_token = new FungibleTokenWrapper("TOKEN", "TKN"); + vm.expectRevert("Initialized: Not initialized"); + new_token.wrap(address(0x0), 0); + } + function test_setup() public { assertEq(token.isNativeAllowed(), true); assertEq(token.name(), "TOKEN"); diff --git a/packages/contracts/contracts/test/TokenWrapperHandler.t.sol b/packages/contracts/contracts/test/TokenWrapperHandler.t.sol new file mode 100644 index 000000000..fd2d3ef8c --- /dev/null +++ b/packages/contracts/contracts/test/TokenWrapperHandler.t.sol @@ -0,0 +1,6 @@ +/** + * Copyright 2021-2023 Webb Technologies + * SPDX-License-Identifier: MIT OR Apache-2.0 + */ + +pragma solidity ^0.8.18; \ No newline at end of file diff --git a/packages/contracts/contracts/test/TreasuryHandler.t.sol b/packages/contracts/contracts/test/TreasuryHandler.t.sol new file mode 100644 index 000000000..fd2d3ef8c --- /dev/null +++ b/packages/contracts/contracts/test/TreasuryHandler.t.sol @@ -0,0 +1,6 @@ +/** + * Copyright 2021-2023 Webb Technologies + * SPDX-License-Identifier: MIT OR Apache-2.0 + */ + +pragma solidity ^0.8.18; \ No newline at end of file diff --git a/packages/contracts/contracts/tokens/AaveTokenWrapper.sol b/packages/contracts/contracts/tokens/AaveTokenWrapper.sol index 38af69168..0f6bd3dbc 100644 --- a/packages/contracts/contracts/tokens/AaveTokenWrapper.sol +++ b/packages/contracts/contracts/tokens/AaveTokenWrapper.sol @@ -46,13 +46,13 @@ contract AaveTokenWrapper is FungibleTokenWrapper, IAaveTokenWrapper { } /// @inheritdoc IAaveTokenWrapper - function deposit(address _tokenAddress, uint256 _amount) external override { + function deposit(address _tokenAddress, uint256 _amount) onlyInitialized external override { IERC20(_tokenAddress).approve(address(aaveLendingPool), _amount); aaveLendingPool.deposit(_tokenAddress, _amount, address(this), 0); } /// @inheritdoc IAaveTokenWrapper - function withdraw(address _tokenAddress, uint256 _amount) external override { + function withdraw(address _tokenAddress, uint256 _amount) onlyInitialized external override { uint256 tokenBalance = IERC20(_tokenAddress).balanceOf(address(this)); aaveLendingPool.withdraw(_tokenAddress, _amount, address(this)); uint256 redeemed = IERC20(_tokenAddress).balanceOf(address(this)) - tokenBalance; diff --git a/packages/contracts/contracts/tokens/FungibleTokenWrapper.sol b/packages/contracts/contracts/tokens/FungibleTokenWrapper.sol index 1de092c6e..35a4810fb 100644 --- a/packages/contracts/contracts/tokens/FungibleTokenWrapper.sol +++ b/packages/contracts/contracts/tokens/FungibleTokenWrapper.sol @@ -7,7 +7,6 @@ pragma solidity ^0.8.18; import "./TokenWrapper.sol"; import "../interfaces/tokens/IFungibleTokenWrapper.sol"; -import "../utils/Initialized.sol"; import "../utils/ProposalNonceTracker.sol"; /// @title A governed TokenWrapper system using an external `handler` address @@ -16,7 +15,6 @@ import "../utils/ProposalNonceTracker.sol"; /// sets fees for wrapping into itself. This contract is intended to be used with /// TokenHandler contract. contract FungibleTokenWrapper is - Initialized, TokenWrapper, IFungibleTokenWrapper, ProposalNonceTracker @@ -77,7 +75,7 @@ contract FungibleTokenWrapper is /// @notice Sets the handler of the FungibleTokenWrapper contract /// @param _handler The address of the new handler /// @notice Only the handler can call this function - function setHandler(address _handler) public onlyHandler { + function setHandler(address _handler) public onlyHandler onlyInitialized { require(_handler != address(0), "FungibleTokenWrapper: Handler Address can't be 0"); handler = _handler; emit HandlerUpdated(_handler); @@ -86,7 +84,7 @@ contract FungibleTokenWrapper is /// @notice Sets whether native tokens are allowed to be wrapped /// @param _isNativeAllowed Whether or not native tokens are allowed to be wrapped /// @notice Only the handler can call this function - function setNativeAllowed(bool _isNativeAllowed) public onlyHandler { + function setNativeAllowed(bool _isNativeAllowed) public onlyHandler onlyInitialized { isNativeAllowed = _isNativeAllowed; emit NativeAllowed(_isNativeAllowed); } @@ -98,7 +96,7 @@ contract FungibleTokenWrapper is function add( address _tokenAddress, uint32 _nonce - ) external override onlyHandler onlyIncrementingByOne(_nonce) { + ) external override onlyHandler onlyInitialized onlyIncrementingByOne(_nonce) { require(!valid[_tokenAddress], "FungibleTokenWrapper: Token should not be valid"); tokens.push(_tokenAddress); @@ -117,7 +115,7 @@ contract FungibleTokenWrapper is function remove( address _tokenAddress, uint32 _nonce - ) external override onlyHandler onlyIncrementingByOne(_nonce) { + ) external override onlyHandler onlyInitialized onlyIncrementingByOne(_nonce) { require(valid[_tokenAddress], "FungibleTokenWrapper: Token should be valid"); uint index = 0; for (uint i = 0; i < tokens.length; i++) { @@ -139,7 +137,7 @@ contract FungibleTokenWrapper is function setFee( uint16 _feePercentage, uint32 _nonce - ) external override onlyHandler onlyIncrementingByOne(_nonce) { + ) external override onlyHandler onlyInitialized onlyIncrementingByOne(_nonce) { require(_feePercentage < 10_000, "FungibleTokenWrapper: Invalid fee percentage"); feePercentage = _feePercentage; emit FeeUpdated(_feePercentage); @@ -152,7 +150,7 @@ contract FungibleTokenWrapper is function setFeeRecipient( address payable _feeRecipient, uint32 _nonce - ) external override onlyHandler onlyIncrementingByOne(_nonce) { + ) external override onlyHandler onlyInitialized onlyIncrementingByOne(_nonce) { require( _feeRecipient != address(0), "FungibleTokenWrapper: Fee Recipient cannot be zero address" diff --git a/packages/contracts/contracts/tokens/TokenWrapper.sol b/packages/contracts/contracts/tokens/TokenWrapper.sol index 09885fa69..3dd9de056 100644 --- a/packages/contracts/contracts/tokens/TokenWrapper.sol +++ b/packages/contracts/contracts/tokens/TokenWrapper.sol @@ -5,6 +5,7 @@ pragma solidity ^0.8.18; +import "../utils/Initialized.sol"; import "../interfaces/tokens/ITokenWrapper.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; @@ -15,7 +16,7 @@ import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; /// @title A token that allows ERC20s to wrap into and mint it. /// @author Webb Technologies. /// @notice This contract is intended to be used with TokenHandler/FungibleToken contract. -abstract contract TokenWrapper is ERC20PresetMinterPauser, ITokenWrapper, ReentrancyGuard { +abstract contract TokenWrapper is ERC20PresetMinterPauser, Initialized, ReentrancyGuard, ITokenWrapper { using SafeERC20 for IERC20; uint16 public feePercentage; address payable public feeRecipient; @@ -71,7 +72,7 @@ abstract contract TokenWrapper is ERC20PresetMinterPauser, ITokenWrapper, Reentr function wrap( address tokenAddress, uint256 amount - ) public payable override nonReentrant isValidWrapping(tokenAddress, feeRecipient, amount) { + ) public payable override nonReentrant onlyInitialized isValidWrapping(tokenAddress, feeRecipient, amount) { _wrapForAndSendTo(_msgSender(), tokenAddress, amount, _msgSender()); } @@ -88,6 +89,7 @@ abstract contract TokenWrapper is ERC20PresetMinterPauser, ITokenWrapper, Reentr payable override nonReentrant + onlyInitialized isMinter isValidWrapping(tokenAddress, feeRecipient, amount) { @@ -109,6 +111,7 @@ abstract contract TokenWrapper is ERC20PresetMinterPauser, ITokenWrapper, Reentr payable override nonReentrant + onlyInitialized isMinter isValidWrapping(tokenAddress, feeRecipient, amount) { @@ -121,7 +124,7 @@ abstract contract TokenWrapper is ERC20PresetMinterPauser, ITokenWrapper, Reentr function unwrap( address tokenAddress, uint256 amount - ) public override nonReentrant isValidUnwrapping(tokenAddress, amount) { + ) public override nonReentrant onlyInitialized isValidUnwrapping(tokenAddress, amount) { _unwrapAndSendTo(_msgSender(), tokenAddress, amount, _msgSender()); } @@ -132,7 +135,7 @@ abstract contract TokenWrapper is ERC20PresetMinterPauser, ITokenWrapper, Reentr address tokenAddress, uint256 amount, address recipient - ) public override nonReentrant isValidUnwrapping(tokenAddress, amount) { + ) public override nonReentrant onlyInitialized isValidUnwrapping(tokenAddress, amount) { _unwrapAndSendTo(_msgSender(), tokenAddress, amount, recipient); } @@ -144,7 +147,7 @@ abstract contract TokenWrapper is ERC20PresetMinterPauser, ITokenWrapper, Reentr address sender, address tokenAddress, uint256 amount - ) public override nonReentrant isMinter isValidUnwrapping(tokenAddress, amount) { + ) public override nonReentrant onlyInitialized isMinter isValidUnwrapping(tokenAddress, amount) { _unwrapAndSendTo(sender, tokenAddress, amount, sender); } diff --git a/packages/contracts/test/governance/governable.test.ts b/packages/contracts/test/governance/governable.test.ts index a1c64404d..c967266f5 100644 --- a/packages/contracts/test/governance/governable.test.ts +++ b/packages/contracts/test/governance/governable.test.ts @@ -6,7 +6,7 @@ const assert = require('assert'); import { ethers, network } from 'hardhat'; import BN from 'bn.js'; -import { toFixedHex, toHex } from '@webb-tools/sdk-core'; +import { toFixedHex, toHex } from '@webb-tools/utils'; import EC from 'elliptic'; const ec = new EC.ec('secp256k1'); const TruffleAssert = require('truffle-assertions'); diff --git a/packages/contracts/test/trees/MerkleForest.test.ts b/packages/contracts/test/trees/MerkleForest.test.ts index ad7f84391..1346e0dde 100644 --- a/packages/contracts/test/trees/MerkleForest.test.ts +++ b/packages/contracts/test/trees/MerkleForest.test.ts @@ -4,7 +4,7 @@ */ import { PoseidonHasher } from '@webb-tools/anchors'; -import { MerkleTree, toFixedHex } from '@webb-tools/sdk-core'; +import { MerkleTree, toFixedHex } from '@webb-tools/utils'; import { poseidon_gencontract as poseidonContract } from 'circomlibjs'; import { BigNumber } from 'ethers'; import { ethers } from 'hardhat'; diff --git a/packages/contracts/test/trees/MerkleTreePoseidon.test.ts b/packages/contracts/test/trees/MerkleTreePoseidon.test.ts index c80433c16..9b58675dc 100644 --- a/packages/contracts/test/trees/MerkleTreePoseidon.test.ts +++ b/packages/contracts/test/trees/MerkleTreePoseidon.test.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: MIT OR Apache-2.0 */ -import { MerkleTree, toFixedHex } from '@webb-tools/sdk-core'; +import { MerkleTree, toFixedHex } from '@webb-tools/utils'; import { BigNumber } from 'ethers'; import { artifacts, contract, ethers } from 'hardhat'; import { poseidon } from 'circomlibjs'; diff --git a/packages/contracts/test/vanchor/ChainalysisVAnchor.test.ts b/packages/contracts/test/vanchor/ChainalysisVAnchor.test.ts index f431e759e..9a538be21 100644 --- a/packages/contracts/test/vanchor/ChainalysisVAnchor.test.ts +++ b/packages/contracts/test/vanchor/ChainalysisVAnchor.test.ts @@ -10,7 +10,7 @@ const { BigNumber } = require('ethers'); import { ChainalysisVAnchor, PoseidonHasher, VAnchor, Verifier } from '@webb-tools/anchors'; import { getChainIdType, hexToU8a, vanchorFixtures, ZkComponents } from '@webb-tools/utils'; -import { Keypair, CircomUtxo, randomBN } from '@webb-tools/sdk-core'; +import { Keypair, Utxo, randomBN } from '@webb-tools/utils'; import { ERC20PresetMinterPauser, ERC20PresetMinterPauser__factory } from '@webb-tools/contracts'; import { expect } from 'chai'; @@ -90,7 +90,7 @@ describe.skip('ChainalysisVAnchor', () => { const randomKeypair = new Keypair(); const amountString = amount ? amount.toString() : '0'; - return CircomUtxo.generateUtxo({ + return Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: chainId.toString(), diff --git a/packages/contracts/test/vanchor/RateLimitedVAnchor.test.ts b/packages/contracts/test/vanchor/RateLimitedVAnchor.test.ts index ec10301e7..2be5f5ed0 100644 --- a/packages/contracts/test/vanchor/RateLimitedVAnchor.test.ts +++ b/packages/contracts/test/vanchor/RateLimitedVAnchor.test.ts @@ -17,7 +17,7 @@ import { hexToU8a, getChainIdType, ZkComponents, vanchorFixtures } from '@webb-t import { BigNumber } from 'ethers'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; -import { Keypair, randomBN, CircomUtxo } from '@webb-tools/sdk-core'; +import { Keypair, randomBN, Utxo } from '@webb-tools/utils'; import { PoseidonHasher, RateLimitedVAnchor } from '@webb-tools/anchors'; import { Verifier } from '@webb-tools/anchors'; @@ -42,7 +42,7 @@ describe('Rate Limited VAnchor', () => { const randomKeypair = new Keypair(); const amountString = amount ? amount.toString() : '0'; - return CircomUtxo.generateUtxo({ + return Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: chainId.toString(), @@ -142,7 +142,7 @@ describe('Rate Limited VAnchor', () => { const aliceWithdrawAmount = 5e6; const aliceChangeAmount = aliceDepositAmount - aliceWithdrawAmount; - const aliceChangeUtxo = await CircomUtxo.generateUtxo({ + const aliceChangeUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: chainID.toString(), @@ -185,7 +185,7 @@ describe('Rate Limited VAnchor', () => { await anchor.setDailyWithdrawalLimit(BigNumber.from(`${5e6}`)); const aliceWithdrawAmount = 6e6; const aliceChangeAmount = aliceDepositAmount - aliceWithdrawAmount; - const aliceChangeUtxo = await CircomUtxo.generateUtxo({ + const aliceChangeUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: chainID.toString(), diff --git a/packages/contracts/test/vanchor/mocks/SetupTxVAnchorMock.ts b/packages/contracts/test/vanchor/mocks/SetupTxVAnchorMock.ts deleted file mode 100644 index 4a3d42599..000000000 --- a/packages/contracts/test/vanchor/mocks/SetupTxVAnchorMock.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { VAnchor } from '@webb-tools/anchors'; - -import { BigNumber, BigNumberish, ethers } from 'ethers'; -import { - toFixedHex, - Utxo, - CircomProvingManager, - ProvingManagerSetupInput, - FIELD_SIZE, - LeafIdentifier, -} from '@webb-tools/sdk-core'; -import { IVariableAnchorExtData, IVariableAnchorPublicInputs } from '@webb-tools/interfaces'; -import { hexToU8a, u8aToHex, getChainIdType, ZkComponents } from '@webb-tools/utils'; -import { VAnchorTree as VAnchorContract } from '@webb-tools/contracts'; - -export class SetupTxVAnchorMock extends VAnchor { - private rootsForProof: BigNumber[]; - - constructor( - contract: VAnchorContract, - signer: ethers.Signer, - treeHeight: number, - maxEdges: number, - smallCircuitZkComponents: ZkComponents, - largeCircuitZkComponents: ZkComponents, - roots: BigNumber[] - ) { - super( - contract, - signer, - treeHeight, - maxEdges, - smallCircuitZkComponents, - largeCircuitZkComponents - ); - this.rootsForProof = roots; - } - - public async setupTransaction( - inputs: Utxo[], - outputs: Utxo[], - fee: BigNumberish, - refund: BigNumberish, - recipient: string, - relayer: string, - wrapUnwrapToken: string, - leavesMap: Record - ) { - // first, check if the merkle root is known on chain - if not, then update - inputs = await this.padUtxos(inputs, 16); - outputs = await this.padUtxos(outputs, 2); - let extAmount = this.getExtAmount(inputs, outputs, fee); - - const chainId = getChainIdType(await this.signer.getChainId()); - - // calculate the sum of input notes (for calculating the public amount) - let sumInputs: BigNumberish = 0; - let leafIds: LeafIdentifier[] = []; - - for (const inputUtxo of inputs) { - sumInputs = BigNumber.from(sumInputs).add(inputUtxo.amount); - leafIds.push({ - index: inputUtxo.index, - typedChainId: Number(inputUtxo.originChainId), - }); - } - - const encryptedCommitments: [Uint8Array, Uint8Array] = [ - hexToU8a(outputs[0].encrypt()), - hexToU8a(outputs[1].encrypt()), - ]; - - const proofInput: ProvingManagerSetupInput<'vanchor'> = { - inputUtxos: inputs, - leavesMap, - leafIds, - roots: this.rootsForProof.map((root) => hexToU8a(root.toHexString())), - chainId: chainId.toString(), - output: [outputs[0], outputs[1]], - encryptedCommitments, - publicAmount: BigNumber.from(extAmount).sub(fee).add(FIELD_SIZE).mod(FIELD_SIZE).toString(), - provingKey: - inputs.length > 2 ? this.largeCircuitZkComponents.zkey : this.smallCircuitZkComponents.zkey, - relayer: hexToU8a(relayer), - refund: BigNumber.from(refund).toString(), - token: hexToU8a(wrapUnwrapToken), - recipient: hexToU8a(recipient), - extAmount: toFixedHex(BigNumber.from(extAmount)), - fee: BigNumber.from(fee).toString(), - }; - - inputs.length > 2 - ? (this.provingManager = new CircomProvingManager( - this.largeCircuitZkComponents.wasm, - this.tree.levels, - null - )) - : (this.provingManager = new CircomProvingManager( - this.smallCircuitZkComponents.wasm, - this.tree.levels, - null - )); - - const proof = await this.provingManager.prove('vanchor', proofInput); - const publicInputs: IVariableAnchorPublicInputs = this.generatePublicInputs( - proof.proof, - this.rootsForProof, - inputs, - outputs, - proofInput.publicAmount, - proof.extDataHash - ); - - const extData: IVariableAnchorExtData = { - recipient: toFixedHex(proofInput.recipient, 20), - extAmount: toFixedHex(proofInput.extAmount), - relayer: toFixedHex(proofInput.relayer, 20), - fee: toFixedHex(proofInput.fee), - refund: toFixedHex(proofInput.refund), - token: toFixedHex(proofInput.token, 20), - encryptedOutput1: u8aToHex(proofInput.encryptedCommitments[0]), - encryptedOutput2: u8aToHex(proofInput.encryptedCommitments[1]), - }; - - return { - extAmount, - extData, - publicInputs, - }; - } -} diff --git a/packages/contracts/test/vanchor/vanchor.test.ts b/packages/contracts/test/vanchor/vanchor.test.ts index 2561e4974..270092d18 100644 --- a/packages/contracts/test/vanchor/vanchor.test.ts +++ b/packages/contracts/test/vanchor/vanchor.test.ts @@ -33,11 +33,9 @@ import { generateVariableWitnessInput, getVAnchorExtDataHash, generateWithdrawProofCallData, - CircomUtxo, -} from '@webb-tools/sdk-core'; +} from '@webb-tools/utils'; import { VAnchor, PoseidonHasher } from '@webb-tools/anchors'; import { Verifier } from '@webb-tools/anchors'; -import { SetupTxVAnchorMock } from './mocks/SetupTxVAnchorMock'; import { retryPromiseMock } from './mocks/retryPromiseMock'; const BN = require('bn.js'); @@ -67,7 +65,7 @@ describe('VAnchor for 1 max edge', () => { const randomKeypair = new Keypair(); const amountString = amount ? amount.toString() : '0'; - return CircomUtxo.generateUtxo({ + return Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: chainId.toString(), @@ -302,7 +300,7 @@ describe('VAnchor for 1 max edge', () => { {} ); - const aliceRefreshUtxo = await CircomUtxo.generateUtxo({ + const aliceRefreshUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: BigNumber.from(chainID).toString(), @@ -338,7 +336,7 @@ describe('VAnchor for 1 max edge', () => { const ethBalanceBefore = await ethers.provider.getBalance(recipient); - const aliceWithdrawUtxo = await CircomUtxo.generateUtxo({ + const aliceWithdrawUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: BigNumber.from(chainID).toString(), @@ -417,7 +415,7 @@ describe('VAnchor for 1 max edge', () => { {} ); - const aliceRefreshUtxo = await CircomUtxo.generateUtxo({ + const aliceRefreshUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: BigNumber.from(chainID).toString(), @@ -506,7 +504,7 @@ describe('VAnchor for 1 max edge', () => { let anchorLeaves = anchor.tree.elements().map((leaf) => hexToU8a(leaf.toHexString())); const aliceDepositAmount2 = 1e7; - let aliceDepositUtxo2 = await CircomUtxo.generateUtxo({ + let aliceDepositUtxo2 = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: chainID.toString(), @@ -549,7 +547,7 @@ describe('VAnchor for 1 max edge', () => { const aliceDepositAmount1 = 1e7; let aliceDepositUtxo1 = await generateUTXOForTest(chainID, aliceDepositAmount1); const aliceDepositAmount2 = 1e7; - let aliceDepositUtxo2 = await CircomUtxo.generateUtxo({ + let aliceDepositUtxo2 = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: chainID.toString(), @@ -574,7 +572,7 @@ describe('VAnchor for 1 max edge', () => { let anchorLeaves = anchor.tree.elements().map((leaf) => hexToU8a(leaf.toHexString())); const aliceDepositAmount3 = 1e7; - let aliceDepositUtxo3 = await CircomUtxo.generateUtxo({ + let aliceDepositUtxo3 = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: chainID.toString(), @@ -602,7 +600,7 @@ describe('VAnchor for 1 max edge', () => { anchorLeaves = anchor.tree.elements().map((leaf) => hexToU8a(leaf.toHexString())); const aliceJoinAmount = 3e7; - const aliceJoinUtxo = await CircomUtxo.generateUtxo({ + const aliceJoinUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: chainID.toString(), @@ -658,7 +656,7 @@ describe('VAnchor for 1 max edge', () => { const aliceWithdrawAmount = 5e6; const aliceChangeAmount = aliceDepositAmount - aliceWithdrawAmount; - const aliceChangeUtxo = await CircomUtxo.generateUtxo({ + const aliceChangeUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: chainID.toString(), @@ -692,7 +690,7 @@ describe('VAnchor for 1 max edge', () => { it('should prevent double spend', async () => { const aliceKeypair = new Keypair(); const aliceDepositAmount = 1e7; - let aliceDepositUtxo = await CircomUtxo.generateUtxo({ + let aliceDepositUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: chainID.toString(), @@ -719,7 +717,7 @@ describe('VAnchor for 1 max edge', () => { const aliceDepositIndex = anchor.tree.getIndexByElement(aliceDepositUtxo.commitment); aliceDepositUtxo.setIndex(aliceDepositIndex); - const aliceTransferUtxo = await CircomUtxo.generateUtxo({ + const aliceTransferUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: chainID.toString(), @@ -747,7 +745,7 @@ describe('VAnchor for 1 max edge', () => { const alice = signers[0]; const aliceDepositAmount = 1e7; - const aliceDepositUtxo = await CircomUtxo.generateUtxo({ + const aliceDepositUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: chainID.toString(), @@ -780,7 +778,7 @@ describe('VAnchor for 1 max edge', () => { // Step 3: Alice tries to create a UTXO with more funds than she has in her account const aliceOutputAmount = '100000000000000000000000'; - const aliceOutputUtxo = await CircomUtxo.generateUtxo({ + const aliceOutputUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: chainID.toString(), @@ -1079,7 +1077,7 @@ describe('VAnchor for 1 max edge', () => { const bobPublicKeypair = Keypair.fromString(registeredKeydata); // generate a UTXO that is only spendable by bob - const aliceTransferUtxo = await CircomUtxo.generateUtxo({ + const aliceTransferUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: chainID.toString(), @@ -1110,7 +1108,7 @@ describe('VAnchor for 1 max edge', () => { const utxos = await Promise.all( encryptedCommitments.map(async (enc, index) => { try { - const decryptedUtxo = await CircomUtxo.decrypt(bobKeypair, enc); + const decryptedUtxo = Utxo.decrypt(bobKeypair, enc); // In order to properly calculate the nullifier, an index is required. decryptedUtxo.setIndex(index); decryptedUtxo.setOriginChainId(chainID.toString()); @@ -1205,99 +1203,6 @@ describe('VAnchor for 1 max edge', () => { // in report we can see the tx with NewCommitment event (this is how alice got money) // and the tx with NewNullifier event is where alice spent the UTXO }); - - it('should reject proofs made against roots of empty edges', async () => { - // This test has not been linked to another anchor - edgeList should be empty. - await TruffleAssert.reverts(anchor.contract.edgeList(0)); - - // create the UTXO for commitment into a fake tree. - const depositAmount = 1e7; - const fakeChainId = getChainIdType(666); - const keypair = new Keypair(); - let fakeUtxo = await CircomUtxo.generateUtxo({ - curve: 'Bn254', - backend: 'Circom', - chainId: chainID.toString(), - originChainId: fakeChainId.toString(), - amount: depositAmount.toString(), - index: '0', - keypair, - }); - - // Attempt to withdraw by creating a proof against a root that shouldn't exist. - // create the merkle tree - const fakeTree = new MerkleTree(30); - const fakeCommitment = u8aToHex(fakeUtxo.commitment); - fakeTree.insert(fakeCommitment); - - const fakeRoot = fakeTree.root(); - - const roots = await anchor.populateRootsForProof(); - roots[1] = fakeRoot; - - const setupVAnchor = new SetupTxVAnchorMock( - anchor.contract, - anchor.signer, - 30, - 1, - anchor.smallCircuitZkComponents, - anchor.largeCircuitZkComponents, - roots - ); - setupVAnchor.token = anchor.token; - let inputs: Utxo[] = [ - fakeUtxo, - await CircomUtxo.generateUtxo({ - curve: 'Bn254', - backend: 'Circom', - chainId: chainID.toString(), - originChainId: chainID.toString(), - amount: '0', - blinding: hexToU8a(randomBN(31).toHexString()), - keypair, - }), - ]; - - let outputs: [Utxo, Utxo] = [ - await CircomUtxo.generateUtxo({ - curve: 'Bn254', - backend: 'Circom', - chainId: chainID.toString(), - originChainId: chainID.toString(), - amount: '0', - keypair, - }), - await CircomUtxo.generateUtxo({ - curve: 'Bn254', - backend: 'Circom', - chainId: chainID.toString(), - originChainId: chainID.toString(), - amount: '0', - keypair, - }), - ]; - - const { publicInputs, extData } = await setupVAnchor.setupTransaction( - inputs, - outputs, - 0, - 0, - recipient, - '0', - setupVAnchor.token, - { - [fakeChainId.toString()]: [fakeUtxo.commitment], - [chainID.toString()]: [hexToU8a(fakeTree.zeroElement.toHexString())], - } - ); - await TruffleAssert.reverts( - anchor.contract.transact(publicInputs.proof, ZERO_BYTES32, extData, publicInputs, { - encryptedOutput1: outputs[0].encrypt(), - encryptedOutput2: outputs[1].encrypt(), - }), - 'LinkableAnchor: non-existent edge is not set to the default root' - ); - }); }); describe('#wrapping tests', () => { @@ -1352,7 +1257,7 @@ describe('VAnchor for 1 max edge', () => { const balTokenBeforeDepositSender = await token.balanceOf(sender.address); const aliceDepositAmount = 1e7; - const aliceDepositUtxo = await CircomUtxo.generateUtxo({ + const aliceDepositUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: chainID.toString(), @@ -1430,7 +1335,7 @@ describe('VAnchor for 1 max edge', () => { const aliceDepositAmount = 1e7; const numOfInsertions = 31; for (let i = 0; i < numOfInsertions; i++) { - const aliceDepositUtxo = await CircomUtxo.generateUtxo({ + const aliceDepositUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: chainID.toString(), @@ -1509,7 +1414,7 @@ describe('VAnchor for 1 max edge', () => { const balTokenBeforeDepositSender = await token.balanceOf(sender.address); const aliceDepositAmount = 1e7; const keypair = new Keypair(); - let aliceDepositUtxo = await CircomUtxo.generateUtxo({ + let aliceDepositUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: chainID.toString(), @@ -1533,7 +1438,7 @@ describe('VAnchor for 1 max edge', () => { ); const aliceChangeAmount = 0; - const aliceChangeUtxo = await CircomUtxo.generateUtxo({ + const aliceChangeUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: chainID.toString(), @@ -1615,7 +1520,7 @@ describe('VAnchor for 1 max edge', () => { //Should take a fee when depositing //Deposit 2e7 and Check Relevant Balances const aliceDepositAmount = 2e7; - let aliceDepositUtxo = await CircomUtxo.generateUtxo({ + let aliceDepositUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: chainID.toString(), @@ -1663,7 +1568,7 @@ describe('VAnchor for 1 max edge', () => { const aliceWithdrawAmount = 1e7; let anchorLeaves = wrappedVAnchor.tree.elements().map((leaf) => hexToU8a(leaf.toHexString())); - let aliceChangeUtxo = await CircomUtxo.generateUtxo({ + let aliceChangeUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: chainID.toString(), diff --git a/packages/contracts/test/vanchor/vanchorForest.test.ts b/packages/contracts/test/vanchor/vanchorForest.test.ts index 596fa90b7..79a493316 100644 --- a/packages/contracts/test/vanchor/vanchorForest.test.ts +++ b/packages/contracts/test/vanchor/vanchorForest.test.ts @@ -33,9 +33,9 @@ import { generateVariableWitnessInput, getVAnchorExtDataHash, generateWithdrawProofCallData, - CircomUtxo, + Utxo, toHex, -} from '@webb-tools/sdk-core'; +} from '@webb-tools/utils'; import { VAnchorForest, PoseidonHasher } from '@webb-tools/anchors'; import { ForestVerifier } from '@webb-tools/anchors'; @@ -70,7 +70,7 @@ describe.skip('VAnchorForest for 1 max edge', () => { const randomKeypair = new Keypair(); const amountString = amount ? amount.toString() : '0'; - return CircomUtxo.generateUtxo({ + return Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: chainId.toString(), @@ -304,7 +304,7 @@ describe.skip('VAnchorForest for 1 max edge', () => { {} ); - const aliceRefreshUtxo = await CircomUtxo.generateUtxo({ + const aliceRefreshUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: BigNumber.from(chainID).toString(), @@ -335,7 +335,7 @@ describe.skip('VAnchorForest for 1 max edge', () => { {} ); - const aliceRefreshUtxo = await CircomUtxo.generateUtxo({ + const aliceRefreshUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: BigNumber.from(chainID).toString(), @@ -422,7 +422,7 @@ describe.skip('VAnchorForest for 1 max edge', () => { let anchorLeaves = anchor.tree.elements().map((leaf) => hexToU8a(leaf.toHexString())); const aliceDepositAmount2 = 1e7; - let aliceDepositUtxo2 = await CircomUtxo.generateUtxo({ + let aliceDepositUtxo2 = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: chainID.toString(), @@ -462,7 +462,7 @@ describe.skip('VAnchorForest for 1 max edge', () => { const aliceDepositAmount1 = 1e7; let aliceDepositUtxo1 = await generateUTXOForTest(chainID, aliceDepositAmount1); const aliceDepositAmount2 = 1e7; - let aliceDepositUtxo2 = await CircomUtxo.generateUtxo({ + let aliceDepositUtxo2 = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: chainID.toString(), @@ -485,7 +485,7 @@ describe.skip('VAnchorForest for 1 max edge', () => { ); const aliceDepositAmount3 = 1e7; - let aliceDepositUtxo3 = await CircomUtxo.generateUtxo({ + let aliceDepositUtxo3 = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: chainID.toString(), @@ -497,7 +497,7 @@ describe.skip('VAnchorForest for 1 max edge', () => { await anchor.transact([], [aliceDepositUtxo3], 0, 0, '0', '0', token.address, {}, {}); const aliceJoinAmount = 3e7; - const aliceJoinUtxo = await CircomUtxo.generateUtxo({ + const aliceJoinUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: chainID.toString(), @@ -544,7 +544,7 @@ describe.skip('VAnchorForest for 1 max edge', () => { ); const aliceWithdrawAmount = 5e6; - const aliceChangeUtxo = await CircomUtxo.generateUtxo({ + const aliceChangeUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: chainID.toString(), @@ -574,7 +574,7 @@ describe.skip('VAnchorForest for 1 max edge', () => { it('should prevent double spend', async () => { const aliceKeypair = new Keypair(); const aliceDepositAmount = 1e7; - let aliceDepositUtxo = await CircomUtxo.generateUtxo({ + let aliceDepositUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: chainID.toString(), @@ -601,7 +601,7 @@ describe.skip('VAnchorForest for 1 max edge', () => { const aliceDepositIndex = anchor.tree.getIndexByElement(aliceDepositUtxo.commitment); aliceDepositUtxo.setIndex(aliceDepositIndex); - const aliceTransferUtxo = await CircomUtxo.generateUtxo({ + const aliceTransferUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: chainID.toString(), @@ -625,7 +625,7 @@ describe.skip('VAnchorForest for 1 max edge', () => { const alice = signers[0]; const aliceDepositAmount = 1e7; - const aliceDepositUtxo = await CircomUtxo.generateUtxo({ + const aliceDepositUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: chainID.toString(), @@ -656,7 +656,7 @@ describe.skip('VAnchorForest for 1 max edge', () => { //Step 3: Alice tries to create a UTXO with more funds than she has in her account const aliceOutputAmount = '100000000000000000000000'; - const aliceOutputUtxo = await CircomUtxo.generateUtxo({ + const aliceOutputUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: chainID.toString(), @@ -966,7 +966,7 @@ describe.skip('VAnchorForest for 1 max edge', () => { const bobPublicKeypair = Keypair.fromString(registeredKeydata); // generate a UTXO that is only spendable by bob - const aliceTransferUtxo = await CircomUtxo.generateUtxo({ + const aliceTransferUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: chainID.toString(), @@ -997,7 +997,7 @@ describe.skip('VAnchorForest for 1 max edge', () => { const utxos = await Promise.all( encryptedCommitments.map(async (enc, index) => { try { - const decryptedUtxo = await CircomUtxo.decrypt(bobKeypair, enc); + const decryptedUtxo = Utxo.decrypt(bobKeypair, enc); // In order to properly calculate the nullifier, an index is required. decryptedUtxo.setIndex(index); decryptedUtxo.setOriginChainId(chainID.toString()); @@ -1089,7 +1089,7 @@ describe.skip('VAnchorForest for 1 max edge', () => { const depositAmount = 1e7; const fakeChainId = getChainIdType(666); const keypair = new Keypair(); - let fakeUtxo = await CircomUtxo.generateUtxo({ + let fakeUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: chainID.toString(), @@ -1281,7 +1281,7 @@ describe.skip('VAnchorForest for 1 max edge', () => { const balTokenBeforeDepositSender = await token.balanceOf(sender.address); const aliceDepositAmount = 1e7; - const aliceDepositUtxo = await CircomUtxo.generateUtxo({ + const aliceDepositUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: chainID.toString(), @@ -1358,7 +1358,7 @@ describe.skip('VAnchorForest for 1 max edge', () => { const aliceDepositAmount = 1e7; const numOfInsertions = 31; for (let i = 0; i < numOfInsertions; i++) { - const aliceDepositUtxo = await CircomUtxo.generateUtxo({ + const aliceDepositUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: chainID.toString(), @@ -1438,7 +1438,7 @@ describe.skip('VAnchorForest for 1 max edge', () => { const balTokenBeforeDepositSender = await token.balanceOf(sender.address); const aliceDepositAmount = 1e7; const keypair = new Keypair(); - let aliceDepositUtxo = await CircomUtxo.generateUtxo({ + let aliceDepositUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: chainID.toString(), @@ -1460,7 +1460,7 @@ describe.skip('VAnchorForest for 1 max edge', () => { ); const aliceChangeAmount = 0; - const aliceChangeUtxo = await CircomUtxo.generateUtxo({ + const aliceChangeUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: chainID.toString(), @@ -1542,7 +1542,7 @@ describe.skip('VAnchorForest for 1 max edge', () => { //Should take a fee when depositing //Deposit 2e7 and Check Relevant Balances const aliceDepositAmount = 2e7; - let aliceDepositUtxo = await CircomUtxo.generateUtxo({ + let aliceDepositUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: chainID.toString(), @@ -1587,7 +1587,7 @@ describe.skip('VAnchorForest for 1 max edge', () => { const aliceWithdrawAmount = 1e7; let anchorLeaves = wrappedVAnchor.tree.elements().map((leaf) => hexToU8a(leaf.toHexString())); - let aliceChangeUtxo = await CircomUtxo.generateUtxo({ + let aliceChangeUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: chainID.toString(), @@ -1857,7 +1857,7 @@ describe.skip('VAnchorForest for 1 max edge', () => { const aliceKeypair = new Keypair(); const aliceDepositAmount = 1e7; // const aliceDepositUtxo = await generateUTXOForTest(chainID2, aliceDepositAmount); - const aliceDepositUtxo = await CircomUtxo.generateUtxo({ + const aliceDepositUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: chainID2.toString(), @@ -1892,7 +1892,7 @@ describe.skip('VAnchorForest for 1 max edge', () => { const aliceKeypair = new Keypair(); const aliceDepositAmount = 1e7; // const aliceDepositUtxo = await generateUTXOForTest(chainID2, aliceDepositAmount); - const aliceDepositUtxo = await CircomUtxo.generateUtxo({ + const aliceDepositUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: chainID.toString(), @@ -1920,7 +1920,7 @@ describe.skip('VAnchorForest for 1 max edge', () => { // making a deposit or the vanchor will not have enough tokens to withdraw const bobKeypair = new Keypair(); // const aliceDepositUtxo = await generateUTXOForTest(chainID2, aliceDepositAmount); - const bobDepositUtxo = await CircomUtxo.generateUtxo({ + const bobDepositUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: chainID.toString(), @@ -1950,7 +1950,7 @@ describe.skip('VAnchorForest for 1 max edge', () => { ); const aliceWithdrawAmount = 5e6; - const aliceChangeUtxo = await CircomUtxo.generateUtxo({ + const aliceChangeUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: chainID2.toString(), diff --git a/packages/contracts/test/vbridge/rescueERC20.test.ts b/packages/contracts/test/vbridge/rescueERC20.test.ts index 1035ff109..cfeda92b4 100644 --- a/packages/contracts/test/vbridge/rescueERC20.test.ts +++ b/packages/contracts/test/vbridge/rescueERC20.test.ts @@ -17,7 +17,7 @@ import { TokenWrapperHandler, } from '@webb-tools/tokens'; import { getChainIdType, vanchorFixtures, ZkComponents } from '@webb-tools/utils'; -import { CircomUtxo, Keypair } from '@webb-tools/sdk-core'; +import { Utxo, Keypair } from '@webb-tools/utils'; import { BigNumber } from 'ethers'; import { HARDHAT_PK_1 } from '../../hardhatAccounts.js'; import { VAnchorTree } from '@webb-tools/contracts'; @@ -149,7 +149,7 @@ describe('Rescue Tokens Tests for ERC20 Tokens', () => { BigNumber.from(1e8).toString() ); // Define inputs/outputs for transact function - const depositUtxo = await CircomUtxo.generateUtxo({ + const depositUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', amount: depositAmount.toString(), diff --git a/packages/contracts/test/vbridge/rescueNative.test.ts b/packages/contracts/test/vbridge/rescueNative.test.ts index c986be8a5..f18f236b0 100644 --- a/packages/contracts/test/vbridge/rescueNative.test.ts +++ b/packages/contracts/test/vbridge/rescueNative.test.ts @@ -16,7 +16,7 @@ import { TokenWrapperHandler, } from '@webb-tools/tokens'; import { getChainIdType, vanchorFixtures, ZkComponents } from '@webb-tools/utils'; -import { CircomUtxo, Keypair } from '@webb-tools/sdk-core'; +import { Utxo, Keypair } from '@webb-tools/utils'; import { BigNumber } from 'ethers'; import { HARDHAT_PK_1 } from '../../hardhatAccounts.js'; import { VAnchorTree } from '@webb-tools/contracts'; @@ -134,7 +134,7 @@ describe('Rescue Tokens Tests for Native ETH', () => { ); // Define inputs/outputs for transact function - const depositUtxo = await CircomUtxo.generateUtxo({ + const depositUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', amount: depositAmount.toString(), diff --git a/packages/contracts/test/vbridge/signatureVBridge.test.ts b/packages/contracts/test/vbridge/signatureVBridge.test.ts index d510ba672..e34c66132 100644 --- a/packages/contracts/test/vbridge/signatureVBridge.test.ts +++ b/packages/contracts/test/vbridge/signatureVBridge.test.ts @@ -12,7 +12,7 @@ import { VAnchor } from '@webb-tools/anchors'; import { MintableToken, FungibleTokenWrapper } from '@webb-tools/tokens'; import { BigNumber } from 'ethers'; import { getChainIdType, vanchorFixtures, ZkComponents } from '@webb-tools/utils'; -import { CircomUtxo } from '@webb-tools/sdk-core'; +import { Utxo } from '@webb-tools/utils'; import { DeployerConfig, GovernorConfig } from '@webb-tools/interfaces'; import { HARDHAT_PK_1 } from '../../hardhatAccounts.js'; import { startGanacheServer } from '../startGanache'; @@ -116,7 +116,7 @@ describe('2-sided multichain tests for signature vbridge', () => { // Get the state of anchors before deposit const sourceAnchorRootBefore = await vAnchor1.contract.getLastRoot(); // Define inputs/outputs for transact function - const depositUtxo = await CircomUtxo.generateUtxo({ + const depositUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', amount: (1e7).toString(), @@ -134,7 +134,7 @@ describe('2-sided multichain tests for signature vbridge', () => { assert.notEqual(sourceAnchorRootAfter, sourceAnchorRootBefore); assert.deepEqual(ethers.BigNumber.from(1), destAnchorEdgeAfter.latestLeafIndex); - const transferUtxo = await CircomUtxo.generateUtxo({ + const transferUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: chainID1.toString(), @@ -237,14 +237,14 @@ describe('2-sided multichain tests for signature vbridge', () => { ); // make one deposit so the edge exists - const depositUtxo1 = await CircomUtxo.generateUtxo({ + const depositUtxo1 = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', amount: (1e7).toString(), originChainId: chainID1.toString(), chainId: chainID2.toString(), }); - const depositUtxo2 = await CircomUtxo.generateUtxo({ + const depositUtxo2 = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', amount: (1e7).toString(), @@ -280,7 +280,7 @@ describe('2-sided multichain tests for signature vbridge', () => { const signers2BalanceBefore = await webbToken1.getBalance(await signers[2].getAddress()); //ganacheWallet2 makes a deposit with dest chain chainID1 - const ganacheDepositUtxo = await CircomUtxo.generateUtxo({ + const ganacheDepositUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', amount: (1e7).toString(), @@ -298,7 +298,7 @@ describe('2-sided multichain tests for signature vbridge', () => { ); //withdraw ganacheWallet2 deposit on chainID1 to signers[2] address - const hardhatWithdrawUtxo = await CircomUtxo.generateUtxo({ + const hardhatWithdrawUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', amount: (5e6).toString(), @@ -339,7 +339,7 @@ describe('2-sided multichain tests for signature vbridge', () => { ); //ganacheWallet2 makes a deposit with dest chain chainID1 - const hardhatDepositUtxo = await CircomUtxo.generateUtxo({ + const hardhatDepositUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', amount: (1e7).toString(), @@ -357,7 +357,7 @@ describe('2-sided multichain tests for signature vbridge', () => { ); //withdraw ganacheWallet2 deposit on chainID1 to signers[2] address - const ganacheWithdrawUtxo = await CircomUtxo.generateUtxo({ + const ganacheWithdrawUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', amount: (5e6).toString(), @@ -394,14 +394,14 @@ describe('2-sided multichain tests for signature vbridge', () => { const signers2BalanceBefore = await webbToken1.getBalance(await signers[2].getAddress()); //ganacheWallet2 makes a deposit with dest chain chainID1 - const ganacheDepositUtxo1 = await CircomUtxo.generateUtxo({ + const ganacheDepositUtxo1 = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', amount: (1e7).toString(), originChainId: chainID2.toString(), chainId: chainID1.toString(), }); - const ganacheDepositUtxo2 = await CircomUtxo.generateUtxo({ + const ganacheDepositUtxo2 = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', amount: (1e7).toString(), @@ -421,7 +421,7 @@ describe('2-sided multichain tests for signature vbridge', () => { ); //withdraw ganacheWallet2 deposit on chainID1 to signers[2] address - const hardhatWithdrawUtxo = await CircomUtxo.generateUtxo({ + const hardhatWithdrawUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', amount: (5e6).toString(), @@ -458,14 +458,14 @@ describe('2-sided multichain tests for signature vbridge', () => { const startingBalanceDest = await webbToken1.getBalance(signers[1].address); //ganacheWallet2 makes a deposit with dest chain chainID1 - const ganacheDepositUtxo1 = await CircomUtxo.generateUtxo({ + const ganacheDepositUtxo1 = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', amount: (1e7).toString(), originChainId: chainID2.toString(), chainId: chainID1.toString(), }); - const ganacheDepositUtxo2 = await CircomUtxo.generateUtxo({ + const ganacheDepositUtxo2 = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', amount: (1e7).toString(), @@ -483,7 +483,7 @@ describe('2-sided multichain tests for signature vbridge', () => { ); //withdraw ganacheWallet2 deposit on chainID1 to signers[2] address - const hardhatWithdrawUtxo = await CircomUtxo.generateUtxo({ + const hardhatWithdrawUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', amount: (5e6).toString(), @@ -510,7 +510,7 @@ describe('2-sided multichain tests for signature vbridge', () => { const signers = await ethers.getSigners(); //ganacheWallet2 makes a deposit with dest chain chainID1 - const ganacheDepositUtxo = await CircomUtxo.generateUtxo({ + const ganacheDepositUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', amount: (1e7).toString(), @@ -518,7 +518,7 @@ describe('2-sided multichain tests for signature vbridge', () => { chainId: chainID1.toString(), }); - const hardhatUtxo = await CircomUtxo.generateUtxo({ + const hardhatUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', amount: (1e7).toString(), @@ -552,7 +552,7 @@ describe('2-sided multichain tests for signature vbridge', () => { const webbToken2 = await MintableToken.tokenFromAddress(webbTokenAddress2!, ganacheWallet2); //ganacheWallet2 makes a deposit with dest chain chainID1 - const ganacheDepositUtxo = await CircomUtxo.generateUtxo({ + const ganacheDepositUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', amount: (5e7).toString(), @@ -582,7 +582,7 @@ describe('2-sided multichain tests for signature vbridge', () => { ); //withdrawing 3e7 from ganacheWallet2 deposit on chainID1 should work despite vanchor1 only having 1e7 webb tokens...this indicates that it minted 2e7 webb tokens to make up the balance - const hardhatWithdrawUtxo = await CircomUtxo.generateUtxo({ + const hardhatWithdrawUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', amount: (2e7).toString(), @@ -671,14 +671,14 @@ describe('2-sided multichain tests for signature vbridge', () => { ); // make one deposit so the edge exists - const depositUtxo1 = await CircomUtxo.generateUtxo({ + const depositUtxo1 = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', amount: (1e7).toString(), originChainId: chainID1.toString(), chainId: chainID2.toString(), }); - const depositUtxo2 = await CircomUtxo.generateUtxo({ + const depositUtxo2 = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', amount: (1e7).toString(), @@ -749,7 +749,7 @@ describe('2-sided multichain tests for signature vbridge', () => { const webbTokenAddress = vBridge.getWebbTokenAddress(chainID1); const webbToken = await MintableToken.tokenFromAddress(webbTokenAddress!, signers[1]); //Deposit UTXO - const hardhatDepositUtxo1 = await CircomUtxo.generateUtxo({ + const hardhatDepositUtxo1 = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', amount: (2.5e7).toString(), @@ -779,14 +779,14 @@ describe('2-sided multichain tests for signature vbridge', () => { const webbToken1 = await MintableToken.tokenFromAddress(webbTokenAddress1!, signers[1]); //Deposit UTXO - const ganacheDepositUtxo1 = await CircomUtxo.generateUtxo({ + const ganacheDepositUtxo1 = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', amount: (2.5e7).toString(), originChainId: chainID2.toString(), chainId: chainID1.toString(), }); - const ganacheDepositUtxo2 = await CircomUtxo.generateUtxo({ + const ganacheDepositUtxo2 = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', amount: (2.5e7).toString(), @@ -817,7 +817,7 @@ describe('2-sided multichain tests for signature vbridge', () => { const balWrapper1UnwrappedBefore = await existingToken1.contract.balanceOf( vAnchor1TokenAddr ); - const hardhatWithdrawUtxo = await CircomUtxo.generateUtxo({ + const hardhatWithdrawUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', amount: (1e7).toString(), @@ -864,21 +864,21 @@ describe('2-sided multichain tests for signature vbridge', () => { const webbToken1 = await MintableToken.tokenFromAddress(webbTokenAddress1!, signers[1]); //Deposit UTXO - const ganacheDepositUtxo1 = await CircomUtxo.generateUtxo({ + const ganacheDepositUtxo1 = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', amount: (2e7).toString(), originChainId: chainID2.toString(), chainId: chainID1.toString(), }); - const ganacheDepositUtxo2 = await CircomUtxo.generateUtxo({ + const ganacheDepositUtxo2 = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', amount: (2e7).toString(), originChainId: chainID2.toString(), chainId: chainID1.toString(), }); - const ganacheDepositUtxo3 = await CircomUtxo.generateUtxo({ + const ganacheDepositUtxo3 = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', amount: (2e7).toString(), @@ -936,7 +936,7 @@ describe('2-sided multichain tests for signature vbridge', () => { const balWrapper1UnwrappedBefore = await existingToken1.contract.balanceOf( vAnchor1TokenAddr ); - const hardhatWithdrawUtxo = await CircomUtxo.generateUtxo({ + const hardhatWithdrawUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', amount: (1e7).toString(), @@ -1107,21 +1107,21 @@ describe.skip('8-sided multichain tests for signature vbridge', () => { await webbToken3.mintTokens(ganacheWallet3.address, '100000000000000000000000'); // Call transact at least once so we can query the edge list for assertions - const depositUtxo1 = await CircomUtxo.generateUtxo({ + const depositUtxo1 = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', amount: (1e7).toString(), originChainId: chainID1.toString(), chainId: chainID2.toString(), }); - const depositUtxo2 = await CircomUtxo.generateUtxo({ + const depositUtxo2 = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', amount: (1e7).toString(), originChainId: chainID2.toString(), chainId: chainID3.toString(), }); - const depositUtxo3 = await CircomUtxo.generateUtxo({ + const depositUtxo3 = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', amount: (1e7).toString(), @@ -1147,7 +1147,7 @@ describe.skip('8-sided multichain tests for signature vbridge', () => { const signers2BalanceBefore = await webbToken1.getBalance(await signers[2].getAddress()); //ganacheWallet2 makes a deposit with dest chain chainID1 - const ganacheDepositUtxo = await CircomUtxo.generateUtxo({ + const ganacheDepositUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', amount: (1e7).toString(), @@ -1165,7 +1165,7 @@ describe.skip('8-sided multichain tests for signature vbridge', () => { ); //withdraw ganacheWallet2 deposit on chainID1 to signers[2] address - const hardhatWithdrawUtxo = await CircomUtxo.generateUtxo({ + const hardhatWithdrawUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', amount: (5e6).toString(), @@ -1207,7 +1207,7 @@ describe.skip('8-sided multichain tests for signature vbridge', () => { ); //ganacheWallet2 makes a deposit with dest chain chainID1 - const hardhatDepositUtxo = await CircomUtxo.generateUtxo({ + const hardhatDepositUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', amount: (1e7).toString(), @@ -1225,7 +1225,7 @@ describe.skip('8-sided multichain tests for signature vbridge', () => { ); //withdraw ganacheWallet2 deposit on chainID1 to signers[2] address - const ganacheWithdrawUtxo = await CircomUtxo.generateUtxo({ + const ganacheWithdrawUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', amount: (5e6).toString(), diff --git a/packages/contracts/test/vbridge/signatureVBridgeSide.test.ts b/packages/contracts/test/vbridge/signatureVBridgeSide.test.ts index ad0204198..d2a1a2ac8 100644 --- a/packages/contracts/test/vbridge/signatureVBridgeSide.test.ts +++ b/packages/contracts/test/vbridge/signatureVBridgeSide.test.ts @@ -19,7 +19,7 @@ import { TokenWrapperHandler, } from '@webb-tools/tokens'; import { getChainIdType, vanchorFixtures, ZkComponents } from '@webb-tools/utils'; -import { CircomUtxo, Keypair } from '@webb-tools/sdk-core'; +import { Utxo, Keypair } from '@webb-tools/utils'; import { BigNumber } from 'ethers'; import { HARDHAT_PK_1 } from '../../hardhatAccounts.js'; import { VAnchorTree } from '@webb-tools/contracts'; @@ -119,7 +119,7 @@ describe('SignatureBridgeSide use', () => { ); // Define inputs/outputs for transact function - const depositUtxo = await CircomUtxo.generateUtxo({ + const depositUtxo = Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', amount: BigNumber.from(1e7).toString(), diff --git a/packages/evm-test-utils/package.json b/packages/evm-test-utils/package.json index fc5ad48fb..23930bc11 100644 --- a/packages/evm-test-utils/package.json +++ b/packages/evm-test-utils/package.json @@ -11,7 +11,6 @@ }, "dependencies": { "@webb-tools/interfaces": "^1.0.4", - "@webb-tools/sdk-core": "0.1.4-126", "@webb-tools/utils": "^1.0.4", "@webb-tools/vbridge": "^1.0.4", "ecpair": "^2.0.1", diff --git a/packages/evm-test-utils/src/localEvmChain.ts b/packages/evm-test-utils/src/localEvmChain.ts index c4385d7bb..654fddfe7 100644 --- a/packages/evm-test-utils/src/localEvmChain.ts +++ b/packages/evm-test-utils/src/localEvmChain.ts @@ -1,6 +1,6 @@ import { IVariableAnchorExtData, IVariableAnchorPublicInputs } from '@webb-tools/interfaces'; import { VAnchor } from '@webb-tools/anchors'; -import { CircomUtxo, Keypair, Utxo } from '@webb-tools/sdk-core'; +import { Utxo, Keypair } from '@webb-tools/utils'; import { MintableToken } from '@webb-tools/tokens'; import { getChainIdType, ZkComponents } from '@webb-tools/utils'; import { VBridge, VBridgeInput } from '@webb-tools/vbridge'; @@ -122,7 +122,7 @@ export async function setupVanchorEvmWithdrawTx( }> { const extAmount = ethers.BigNumber.from(0).sub(inputUtxo.amount); - const dummyOutput1 = await CircomUtxo.generateUtxo({ + const dummyOutput1 = Utxo.generateUtxo({ amount: '0', backend: 'Circom', chainId: destChain.chainId.toString(), @@ -130,7 +130,7 @@ export async function setupVanchorEvmWithdrawTx( keypair: spender, }); - const dummyOutput2 = await CircomUtxo.generateUtxo({ + const dummyOutput2 = Utxo.generateUtxo({ amount: '0', backend: 'Circom', chainId: destChain.chainId.toString(), @@ -138,7 +138,7 @@ export async function setupVanchorEvmWithdrawTx( keypair: spender, }); - const dummyInput = await CircomUtxo.generateUtxo({ + const dummyInput = Utxo.generateUtxo({ amount: '0', backend: 'Circom', chainId: destChain.chainId.toString(), diff --git a/packages/interfaces/package.json b/packages/interfaces/package.json index 8bc04a24b..65bbb129a 100644 --- a/packages/interfaces/package.json +++ b/packages/interfaces/package.json @@ -9,7 +9,7 @@ "compile": "tsc -p tsconfig.build.json" }, "dependencies": { - "@webb-tools/sdk-core": "0.1.4-126" + "@webb-tools/utils": "1.0.4" }, "publishConfig": { "access": "public" diff --git a/packages/interfaces/src/IVAnchor.ts b/packages/interfaces/src/IVAnchor.ts index ae53b2271..dcc94c2ed 100644 --- a/packages/interfaces/src/IVAnchor.ts +++ b/packages/interfaces/src/IVAnchor.ts @@ -1,4 +1,4 @@ -import { MerkleProof, Utxo } from '@webb-tools/sdk-core'; +import { MerkleProof, Utxo } from '@webb-tools/utils'; import { ethers, BaseContract } from 'ethers'; export interface IVAnchor { diff --git a/packages/interfaces/src/vanchor/index.ts b/packages/interfaces/src/vanchor/index.ts index 0658cd657..d6b4e0a99 100644 --- a/packages/interfaces/src/vanchor/index.ts +++ b/packages/interfaces/src/vanchor/index.ts @@ -1,5 +1,5 @@ import { BigNumberish, BigNumber } from 'ethers'; -import { Keypair } from '@webb-tools/sdk-core'; +import { Keypair } from '@webb-tools/utils'; export interface IMerkleProofData { pathElements: BigNumberish[]; diff --git a/packages/proposals/LICENSE.Apache-2.0 b/packages/proposals/LICENSE.Apache-2.0 new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/packages/proposals/LICENSE.Apache-2.0 @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/proposals/LICENSE.MIT b/packages/proposals/LICENSE.MIT new file mode 100644 index 000000000..dc3808175 --- /dev/null +++ b/packages/proposals/LICENSE.MIT @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2023 Webb Technologies Ltd. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/packages/proposals/README.md b/packages/proposals/README.md new file mode 100644 index 000000000..69e2c02b6 --- /dev/null +++ b/packages/proposals/README.md @@ -0,0 +1,16 @@ +

Webb Solidity Utils

+ +This sub-module is for defining common functionality / types used across other packages in webb solidity. + +## License + + +Licensed under
Apache 2.0 / MIT license. + + +
+ + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this crate by you, as defined in the MIT OR Apache 2.0 license, shall be licensed as above, without any additional terms or conditions. + + diff --git a/packages/proposals/package.json b/packages/proposals/package.json new file mode 100644 index 000000000..ea33b7344 --- /dev/null +++ b/packages/proposals/package.json @@ -0,0 +1,25 @@ +{ + "name": "@webb-tools/proposals", + "main": "./dist/index.js", + "license": "(MIT OR Apache-2.0)", + "author": "Webb Developers ", + "scripts": { + "build": "yarn run clean && yarn run compile", + "clean": "rimraf -rf ./dist", + "compile": "tsc -p tsconfig.build.json", + "test": "mocha -r ts-node/register 'src/__test__/**/*.ts'" + }, + "dependencies": { + "@webb-tools/utils": "^1.0.4" + }, + "publishConfig": { + "access": "public" + }, + "repository": { + "directory": "packages/proposals", + "type": "git", + "url": "git://github.com/webb-tools/protocol-solidity.git" + }, + "version": "1.0.4", + "gitHead": "e1f3b300b6e004ac5a346dc0458bb1d303969d97" +} diff --git a/packages/proposals/src/ProposalHeader.ts b/packages/proposals/src/ProposalHeader.ts new file mode 100644 index 000000000..26717b0cf --- /dev/null +++ b/packages/proposals/src/ProposalHeader.ts @@ -0,0 +1,78 @@ +import { u8aToHex } from '@webb-tools/utils'; +import { BE } from './'; +import { IResourceId, ResourceId } from './ResourceId'; + +/** + * Proposal Header is the first 40 bytes of any proposal and it contains the following information: + * - resource id (32 bytes) + * - target function signature (4 bytes) + * - nonce (4 bytes). + */ +export interface IProposalHeader { + /** + * 32 bytes ResourceId + */ + readonly resourceId: IResourceId; + /** + * 4 bytes function signature / function identifier + */ + readonly functionSignature: Uint8Array; + /** + * 4 bytes Hex-encoded string of the `nonce` for this proposal. + */ + readonly nonce: number; +} + +/** + * Proposal Header class + */ +export class ProposalHeader implements IProposalHeader { + resourceId: IResourceId; + functionSignature: Uint8Array; + nonce: number; + + constructor(resourceId: IResourceId, functionSignature: Uint8Array, nonce: number) { + this.resourceId = resourceId; + this.functionSignature = functionSignature; + this.nonce = nonce; + } + + /** + * Converts a 40-byte Uint8Array into a proposal header. + */ + static fromBytes(bytes: Uint8Array): ProposalHeader { + if (bytes.length !== 40) { + throw new Error('bytes must be 40 bytes'); + } + + const resourceId = ResourceId.fromBytes(bytes.slice(0, 32)); + const functionSignature = bytes.slice(32, 36); + const nonce = new DataView(bytes.buffer).getUint32(36, BE); + + return new ProposalHeader(resourceId, functionSignature, nonce); + } + + /** + * Converts the proposal header into a 40-byte Uint8Array. + */ + toU8a(): Uint8Array { + const proposalHeader = new Uint8Array(40); + + proposalHeader.set(this.resourceId.toU8a(), 0); + proposalHeader.set(this.functionSignature.slice(0, 4), 32); + const buf = Buffer.allocUnsafe(4); + + buf.writeUInt32BE(this.nonce, 0); + + proposalHeader.set(buf, 36); + + return proposalHeader; + } + + /** + * Converts the proposal header into a 40-byte Hex-encoded string. + */ + toString(): string { + return u8aToHex(this.toU8a()); + } +} diff --git a/packages/proposals/src/ProposalKinds.ts b/packages/proposals/src/ProposalKinds.ts new file mode 100644 index 000000000..67672e853 --- /dev/null +++ b/packages/proposals/src/ProposalKinds.ts @@ -0,0 +1,745 @@ +import { Transaction, utils } from 'ethers'; + +import { BE } from './'; +import { ProposalHeader } from './ProposalHeader'; +import { ResourceId } from './ResourceId'; +import { hexToU8a, u8aToHex } from '@webb-tools/utils'; + +export interface IEVMProposal { + readonly chainId: number; + readonly nonce: number; + readonly tx: Transaction; +} + +export class EVMProposal implements IEVMProposal { + readonly chainId: number; + readonly nonce: number; + readonly tx: Transaction; + + constructor(chainId: number, nonce: number, tx: Transaction) { + this.chainId = chainId; + this.nonce = nonce; + this.tx = tx; + } + + static fromBytes(bytes: Uint8Array): EVMProposal { + const tx = utils.parseTransaction(bytes); + const chainId = tx.chainId || 0; + const nonce = tx.nonce; + + return new EVMProposal(chainId, nonce, tx); + } + + toU8a(): Uint8Array { + const serialized = utils.serializeTransaction(this.tx); + + return hexToU8a(serialized); + } +} + +export interface IRefreshProposal { + readonly nonce: number; + readonly publicKey: string; +} + +export class RefreshProposal implements IRefreshProposal { + voterMerkleRoot: string; + sessionLength: bigint; + voterCount: number; + nonce: number; + publicKey: string; + + constructor( + voterMerkleRoot: string, + sessionLength: bigint, + voterCount: number, + nonce: number, + publicKey: string + ) { + this.voterMerkleRoot = voterMerkleRoot; + this.sessionLength = sessionLength; + this.voterCount = voterCount; + this.nonce = nonce; + this.publicKey = publicKey; + } + + toU8a(): Uint8Array { + const merkleRoot = hexToU8a(this.voterMerkleRoot, 32 * 8); + const publicKey = hexToU8a(this.publicKey); + + const internals = new Uint8Array(8 + 4 + 4); + const dataView = new DataView(internals.buffer); + + dataView.setBigUint64(0, this.sessionLength, BE); + dataView.setUint32(8, this.voterCount, BE); + dataView.setUint32(8 + 4, this.nonce, BE); + + return new Uint8Array([...merkleRoot, ...internals, ...publicKey]); + } + + static fromBytes(bytes: Uint8Array): RefreshProposal { + const merkleRoot = u8aToHex(bytes.slice(0, 32)); + const dataView = new DataView(bytes.buffer); + const sessionLength = dataView.getBigUint64(32, BE); + const voterCount = dataView.getUint32(32 + 8, BE); + const nonce = dataView.getUint32(32 + 12, BE); + const publicKey = bytes.slice(32 + 16, bytes.length); + + return new RefreshProposal(merkleRoot, sessionLength, voterCount, nonce, u8aToHex(publicKey)); + } +} + +export interface IAnchorUpdateProposal { + /** + * The Anchor Proposal Header. + */ + readonly header: ProposalHeader; + /** + * 32 bytes Hex-encoded string of the `merkleRoot`. + */ + readonly merkleRoot: string; + /** + * 32 bytes Hex-encoded string of the `srcResourceId`. + */ + readonly srcResourceId: ResourceId; +} + +export class AnchorUpdateProposal implements IAnchorUpdateProposal { + header: ProposalHeader; + merkleRoot: string; + srcResourceId: ResourceId; + + constructor(header: ProposalHeader, merkleRoot: string, srcResourceId: ResourceId) { + this.header = header; + this.merkleRoot = merkleRoot; + this.srcResourceId = srcResourceId; + } + + static fromBytes(bytes: Uint8Array): AnchorUpdateProposal { + const header = ProposalHeader.fromBytes(bytes.slice(0, 40)); + const merkleRoot = u8aToHex(bytes.slice(40, 72)); + const srcResourceId = ResourceId.fromBytes(bytes.slice(72, 104)); + + return new AnchorUpdateProposal(header, merkleRoot, srcResourceId); + } + + toU8a(): Uint8Array { + const merkleRootBytesLength = 32; + const rIdBytesLength = 32; + const header = this.header.toU8a(); + const updateProposal = new Uint8Array(header.length + merkleRootBytesLength + rIdBytesLength); + + updateProposal.set(header, 0); // 0 -> 40 + + const merkleRoot = hexToU8a(this.merkleRoot, merkleRootBytesLength * 8); + + updateProposal.set(merkleRoot, 40); // 40 -> 72 + updateProposal.set(this.srcResourceId.toU8a(), 72); // 72 -> 104 + + return updateProposal; + } +} + +export interface ITokenAddProposal { + /** + * The Token Add Proposal Header. + + */ + readonly header: ProposalHeader; + /** + * 20 bytes Hex-encoded string. + */ + readonly newTokenAddress: string; +} + +export class TokenAddProposal implements ITokenAddProposal { + header: ProposalHeader; + newTokenAddress: string; + + constructor(header: ProposalHeader, newTokenAddress: string) { + this.header = header; + this.newTokenAddress = newTokenAddress; + } + + static fromBytes(bytes: Uint8Array): TokenAddProposal { + const header = ProposalHeader.fromBytes(bytes.slice(0, 40)); + const newTokenAddress = u8aToHex(bytes.slice(40, 60)); + + return new TokenAddProposal(header, newTokenAddress); + } + + toU8a(): Uint8Array { + const header = this.header.toU8a(); + const addressBytesLength = 20; + const tokenAddProposal = new Uint8Array(header.length + 20); + + tokenAddProposal.set(header, 0); // 0 -> 40 + tokenAddProposal.set(hexToU8a(this.newTokenAddress, addressBytesLength * 8), 40); // 40 -> 60 + + return tokenAddProposal; + } +} + +export interface ITokenRemoveProposal { + /** + * The Token Remove Proposal Header. + */ + readonly header: ProposalHeader; + /** + * 20 bytes Hex-encoded string. + */ + readonly removeTokenAddress: string; +} + +export class TokenRemoveProposal implements ITokenRemoveProposal { + header: ProposalHeader; + removeTokenAddress: string; + + constructor(header: ProposalHeader, removeTokenAddress: string) { + this.header = header; + this.removeTokenAddress = removeTokenAddress; + } + + static fromBytes(bytes: Uint8Array): TokenRemoveProposal { + const header = ProposalHeader.fromBytes(bytes.slice(0, 40)); + const removeTokenAddress = u8aToHex(bytes.slice(40, 60)); + + return new TokenRemoveProposal(header, removeTokenAddress); + } + + toU8a(): Uint8Array { + const header = this.header.toU8a(); + const addressBytesLength = 20; + const tokenRemoveProposal = new Uint8Array(header.length + addressBytesLength); + + tokenRemoveProposal.set(header, 0); // 0 -> 40 + tokenRemoveProposal.set(hexToU8a(this.removeTokenAddress, addressBytesLength * 8), 40); // 40 -> 60 + + return tokenRemoveProposal; + } +} + +export interface IWrappingFeeUpdateProposal { + /** + * The Wrapping Fee Update Proposal Header. + */ + readonly header: ProposalHeader; + /** + * 2 byte Hex-encoded string. + */ + readonly newFee: string; +} + +export class WrappingFeeUpdateProposal implements IWrappingFeeUpdateProposal { + header: ProposalHeader; + newFee: string; + + constructor(header: ProposalHeader, newFee: string) { + this.header = header; + this.newFee = newFee; + } + + static fromBytes(bytes: Uint8Array): WrappingFeeUpdateProposal { + const header = ProposalHeader.fromBytes(bytes.slice(0, 40)); + const newFee = u8aToHex(bytes.slice(40, 42)); + + return new WrappingFeeUpdateProposal(header, newFee); + } + + toU8a(): Uint8Array { + const newFeeBytesLength = 2; + const header = this.header.toU8a(); + const wrappingFeeUpdateProposal = new Uint8Array(header.length + newFeeBytesLength); + + wrappingFeeUpdateProposal.set(header, 0); // 0 -> 40 + wrappingFeeUpdateProposal.set(hexToU8a(this.newFee, newFeeBytesLength * 8), 40); // 40 -> 42 + + return wrappingFeeUpdateProposal; + } +} + +export interface IMinWithdrawalLimitProposal { + /** + * The Wrapping Fee Update Proposal Header. + */ + readonly header: ProposalHeader; + /** + * 32 bytes Hex-encoded string. + */ + readonly minWithdrawalLimitBytes: string; +} + +export class MinWithdrawalLimitProposal implements IMinWithdrawalLimitProposal { + header: ProposalHeader; + minWithdrawalLimitBytes: string; + + constructor(header: ProposalHeader, minWithdrawalLimitBytes: string) { + this.header = header; + this.minWithdrawalLimitBytes = minWithdrawalLimitBytes; + } + + static fromBytes(bytes: Uint8Array): MinWithdrawalLimitProposal { + const header = ProposalHeader.fromBytes(bytes.slice(0, 40)); + const minWithdrawalLimitBytes = u8aToHex(bytes.slice(40, 72)); + + return new MinWithdrawalLimitProposal(header, minWithdrawalLimitBytes); + } + + toU8a(): Uint8Array { + const limitBytesLength = 32; + const header = this.header.toU8a(); + const minWithdrawalLimitProposal = new Uint8Array(header.length + limitBytesLength); + + minWithdrawalLimitProposal.set(header, 0); // 0 -> 40 + minWithdrawalLimitProposal.set( + hexToU8a(this.minWithdrawalLimitBytes, limitBytesLength * 8), + 40 + ); // 40 -> 72 + + return minWithdrawalLimitProposal; + } +} + +export interface IMaxDepositLimitProposal { + /** + * The Wrapping Fee Update Proposal Header. + */ + readonly header: ProposalHeader; + /** + * 32 bytes Hex-encoded string. + */ + readonly maxDepositLimitBytes: string; +} + +export class MaxDepositLimitProposal implements IMaxDepositLimitProposal { + header: ProposalHeader; + maxDepositLimitBytes: string; + + constructor(header: ProposalHeader, maxDepositLimitBytes: string) { + this.header = header; + this.maxDepositLimitBytes = maxDepositLimitBytes; + } + + static fromBytes(bytes: Uint8Array): MaxDepositLimitProposal { + const header = ProposalHeader.fromBytes(bytes.slice(0, 40)); + const maxDepositLimitBytes = u8aToHex(bytes.slice(40, 72)); + + return new MaxDepositLimitProposal(header, maxDepositLimitBytes); + } + + toU8a(): Uint8Array { + const limitBytesLength = 32; + const header = this.header.toU8a(); + const maxDepositLimitProposal = new Uint8Array(header.length + limitBytesLength); + + maxDepositLimitProposal.set(header, 0); // 0 -> 40 + maxDepositLimitProposal.set(hexToU8a(this.maxDepositLimitBytes, limitBytesLength * 8), 40); // 40 -> 72 + + return maxDepositLimitProposal; + } +} + +export interface IResourceIdUpdateProposal { + /** + * The ResourceIdUpdateProposal Header. + */ + readonly header: ProposalHeader; + /** + * 32 bytes Hex-encoded string. + */ + readonly newResourceId: string; + /** + * 20 bytes Hex-encoded string. + */ + readonly handlerAddress: string; +} + +export class ResourceIdUpdateProposal implements IResourceIdUpdateProposal { + header: ProposalHeader; + newResourceId: string; + handlerAddress: string; + + constructor(header: ProposalHeader, newResourceId: string, handlerAddress: string) { + this.header = header; + this.newResourceId = newResourceId; + this.handlerAddress = handlerAddress; + } + + static fromBytes(bytes: Uint8Array): ResourceIdUpdateProposal { + const header = ProposalHeader.fromBytes(bytes.slice(0, 40)); + const newResourceId = u8aToHex(bytes.slice(40, 72)); + const handlerAddress = u8aToHex(bytes.slice(72, 92)); + + return new ResourceIdUpdateProposal(header, newResourceId, handlerAddress); + } + + toU8a(): Uint8Array { + const rIdBytesLength = 32; + const addressBytesLength = 20; + const header = this.header.toU8a(); + const resourceIdUpdateProposal = new Uint8Array( + header.length + rIdBytesLength + addressBytesLength + ); + + resourceIdUpdateProposal.set(header, 0); // 0 -> 40 + resourceIdUpdateProposal.set(hexToU8a(this.newResourceId, rIdBytesLength * 8), 40); // 40 -> 72 + resourceIdUpdateProposal.set(hexToU8a(this.handlerAddress, addressBytesLength * 8), 72); // 72 -> 92 + + return resourceIdUpdateProposal; + } +} + +export interface ISetTreasuryHandlerProposal { + /** + * The Token Add Proposal Header. + */ + readonly header: ProposalHeader; + /** + * 20 bytes Hex-encoded string. + */ + readonly newTreasuryHandler: string; +} + +export class SetTreasuryHandlerProposal implements ISetTreasuryHandlerProposal { + header: ProposalHeader; + newTreasuryHandler: string; + + constructor(header: ProposalHeader, newTreasuryHandler: string) { + this.header = header; + this.newTreasuryHandler = newTreasuryHandler; + } + + static fromBytes(bytes: Uint8Array): SetTreasuryHandlerProposal { + const header = ProposalHeader.fromBytes(bytes.slice(0, 40)); + const newTreasuryHandler = u8aToHex(bytes.slice(40, 60)); + + return new SetTreasuryHandlerProposal(header, newTreasuryHandler); + } + + toU8a(): Uint8Array { + const addressBytesLength = 20; + const header = this.header.toU8a(); + const setTreasuryHandlerProposal = new Uint8Array(header.length + addressBytesLength); + + setTreasuryHandlerProposal.set(header, 0); // 0 -> 40 + setTreasuryHandlerProposal.set(hexToU8a(this.newTreasuryHandler, addressBytesLength * 8), 40); // 40 -> 60 + + return setTreasuryHandlerProposal; + } +} + +export interface ISetVerifierProposal { + /** + * The Token Add Proposal Header. + */ + readonly header: ProposalHeader; + /** + * 20 bytes Hex-encoded string. + */ + readonly newVerifier: string; +} + +export class SetVerifierProposal implements ISetVerifierProposal { + header: ProposalHeader; + newVerifier: string; + + constructor(header: ProposalHeader, newVerifier: string) { + this.header = header; + this.newVerifier = newVerifier; + } + + static fromBytes(bytes: Uint8Array): SetVerifierProposal { + const header = ProposalHeader.fromBytes(bytes.slice(0, 40)); + const newVerifier = u8aToHex(bytes.slice(40, 60)); + + return new SetVerifierProposal(header, newVerifier); + } + + toU8a(): Uint8Array { + const addressBytesLength = 20; + const header = this.header.toU8a(); + const setVerifierProposal = new Uint8Array(header.length + addressBytesLength); + + setVerifierProposal.set(header, 0); // 0 -> 40 + setVerifierProposal.set(hexToU8a(this.newVerifier, addressBytesLength * 8), 40); // 40 -> 60 + + return setVerifierProposal; + } +} + +export interface IFeeRecipientUpdateProposal { + /** + * The Token Add Proposal Header. + */ + readonly header: ProposalHeader; + /** + * 20 bytes Hex-encoded string. + */ + readonly newFeeRecipient: string; +} + +export class FeeRecipientUpdateProposal implements IFeeRecipientUpdateProposal { + header: ProposalHeader; + newFeeRecipient: string; + + constructor(header: ProposalHeader, newFeeRecipient: string) { + this.header = header; + this.newFeeRecipient = newFeeRecipient; + } + + static fromBytes(bytes: Uint8Array): FeeRecipientUpdateProposal { + const header = ProposalHeader.fromBytes(bytes.slice(0, 40)); + const newFeeRecipient = u8aToHex(bytes.slice(40, 60)); + + return new FeeRecipientUpdateProposal(header, newFeeRecipient); + } + + toU8a(): Uint8Array { + const addressBytesLength = 20; + const header = this.header.toU8a(); + const feeRecipientUpdateProposal = new Uint8Array(header.length + addressBytesLength); + + feeRecipientUpdateProposal.set(header, 0); // 0 -> 40 + feeRecipientUpdateProposal.set(hexToU8a(this.newFeeRecipient, addressBytesLength * 8), 40); // 40 -> 60 + + return feeRecipientUpdateProposal; + } +} + +export interface IRescueTokensProposal { + /** + * The Rescue Token Proposals Header. + */ + readonly header: ProposalHeader; + + /** + * 20 bytes Hex-encoded string. + */ + readonly tokenAddress: string; + /** + * 20 bytes Hex-encoded string. + */ + readonly toAddress: string; + /** + * 32 bytes Hex-encoded string. + */ + readonly amount: string; +} + +export class RescueTokensProposal implements IRescueTokensProposal { + header: ProposalHeader; + tokenAddress: string; + toAddress: string; + amount: string; + + constructor(header: ProposalHeader, tokenAddress: string, toAddress: string, amount: string) { + this.header = header; + this.tokenAddress = tokenAddress; + this.toAddress = toAddress; + this.amount = amount; + } + + static fromBytes(bytes: Uint8Array): RescueTokensProposal { + const header = ProposalHeader.fromBytes(bytes.slice(0, 40)); + const tokenAddress = u8aToHex(bytes.slice(40, 60)); + const toAddress = u8aToHex(bytes.slice(60, 80)); + const amount = u8aToHex(bytes.slice(80, 112)); + + return new RescueTokensProposal(header, tokenAddress, toAddress, amount); + } + + toU8a(): Uint8Array { + const addressBytesLength = 20; + const amountBytesLength = 32; + const header = this.header.toU8a(); + const rescueTokensProposal = new Uint8Array( + header.length + addressBytesLength + addressBytesLength + amountBytesLength + ); + + rescueTokensProposal.set(header, 0); // 0 -> 40 + rescueTokensProposal.set(hexToU8a(this.tokenAddress, addressBytesLength * 8), 40); // 40 -> 60 + rescueTokensProposal.set(hexToU8a(this.toAddress, addressBytesLength * 8), 60); // 60 -> 80 + rescueTokensProposal.set(hexToU8a(this.amount, amountBytesLength * 8), 80); // 80 -> 112 + + return rescueTokensProposal; + } +} + +export interface IRegisterFungibleTokenProposal { + /** + * The Rescue Token Proposals Header. + */ + readonly header: ProposalHeader; + + /** + * 20 bytes Hex-encoded string. + */ + readonly tokenHandler: string; + /** + * 4 bytes Hex-encoded string. + */ + readonly assetId: string; + /** + * 32 bytes Hex-encoded string. + */ + readonly name: string; + /** + * 32 bytes Hex-encoded string. + */ + readonly symbol: string; +} + +export class RegisterFungibleTokenProposal implements IRegisterFungibleTokenProposal { + header: ProposalHeader; + tokenHandler: string; + assetId: string; + name: string; + symbol: string; + + constructor( + header: ProposalHeader, + tokenHandler: string, + assetId: string, + name: string, + symbol: string + ) { + this.header = header; + this.tokenHandler = tokenHandler; + this.assetId = assetId; + this.name = name; + this.symbol = symbol; + } + + static fromBytes(bytes: Uint8Array): RegisterFungibleTokenProposal { + const header = ProposalHeader.fromBytes(bytes.slice(0, 40)); + const tokenHandler = u8aToHex(bytes.slice(40, 60)); + const assetId = u8aToHex(bytes.slice(60, 64)); + const name = u8aToHex(bytes.slice(64, 96)); + const symbol = u8aToHex(bytes.slice(96, 128)); + + return new RegisterFungibleTokenProposal(header, tokenHandler, assetId, name, symbol); + } + + toU8a(): Uint8Array { + const header = this.header.toU8a(); + const tokenHandlerBytesLength = 20; + const assetIdBytesLength = 4; + const nameBytesLength = 32; + const symbolBytesLength = 32; + const registerFungibleTokenProposal = new Uint8Array( + header.length + + tokenHandlerBytesLength + + assetIdBytesLength + + nameBytesLength + + symbolBytesLength + ); + + registerFungibleTokenProposal.set(header, 0); // 0 -> 40 + registerFungibleTokenProposal.set(hexToU8a(this.tokenHandler, tokenHandlerBytesLength * 8), 40); // 40 -> 60 + registerFungibleTokenProposal.set(hexToU8a(this.assetId, assetIdBytesLength * 8), 60); // 60 -> 64 + registerFungibleTokenProposal.set(hexToU8a(this.name, nameBytesLength * 8), 64); // 64 -> 96 + registerFungibleTokenProposal.set(hexToU8a(this.symbol, symbolBytesLength * 8), 96); // 96 -> 128 + + return registerFungibleTokenProposal; + } +} + +export interface IRegisterNftTokenProposal { + /** + * The Rescue Token Proposals Header. + */ + readonly header: ProposalHeader; + + /** + * 20 bytes Hex-encoded string. + */ + readonly tokenHandler: string; + /** + * 4 bytes Hex-encoded string. + */ + readonly assetId: string; + /** + * 20 bytes Hex-encoded string. + */ + readonly collectionAddress: string; + /** + * 32 bytes Hex-encoded string. + */ + readonly salt: string; + /** + * 64 bytes Hex-encoded string. + */ + readonly uri: string; +} + +export class RegisterNftTokenProposal implements IRegisterNftTokenProposal { + header: ProposalHeader; + tokenHandler: string; + assetId: string; + collectionAddress: string; + salt: string; + uri: string; + + constructor( + header: ProposalHeader, + tokenHandler: string, + assetId: string, + collectionAddress: string, + salt: string, + uri: string + ) { + this.header = header; + this.tokenHandler = tokenHandler; + this.assetId = assetId; + this.collectionAddress = collectionAddress; + this.salt = salt; + this.uri = uri; + } + + static fromBytes(bytes: Uint8Array): RegisterNftTokenProposal { + const header = ProposalHeader.fromBytes(bytes.slice(0, 40)); + const tokenHandler = u8aToHex(bytes.slice(40, 60)); + const assetId = u8aToHex(bytes.slice(60, 64)); + const collectionAddress = u8aToHex(bytes.slice(64, 84)); + const salt = u8aToHex(bytes.slice(84, 116)); + const uri = u8aToHex(bytes.slice(116, 180)); + + return new RegisterNftTokenProposal( + header, + tokenHandler, + assetId, + collectionAddress, + salt, + uri + ); + } + + toU8a(): Uint8Array { + const header = this.header.toU8a(); + const tokenHandlerBytesLength = 20; + const assetIdBytesLength = 4; + const collectionAddressBytesLength = 20; + const saltBytesLength = 32; + const uriBytesLength = 64; + const registerNftTokenProposal = new Uint8Array( + header.length + + tokenHandlerBytesLength + + assetIdBytesLength + + collectionAddressBytesLength + + saltBytesLength + + uriBytesLength + ); + + registerNftTokenProposal.set(header, 0); // 0 -> 40 + registerNftTokenProposal.set(hexToU8a(this.tokenHandler, tokenHandlerBytesLength * 8), 40); // 40 -> 60 + registerNftTokenProposal.set(hexToU8a(this.assetId, assetIdBytesLength * 8), 60); // 60 -> 64 + registerNftTokenProposal.set( + hexToU8a(this.collectionAddress, collectionAddressBytesLength * 8), + 64 + ); // 64 -> 84 + registerNftTokenProposal.set(hexToU8a(this.salt, saltBytesLength * 8), 84); // 84 -> 116 + registerNftTokenProposal.set(hexToU8a(this.uri, uriBytesLength * 8), 116); // 116 -> 180 + + return registerNftTokenProposal; + } +} diff --git a/packages/proposals/src/ResourceId.ts b/packages/proposals/src/ResourceId.ts new file mode 100644 index 000000000..10454e97c --- /dev/null +++ b/packages/proposals/src/ResourceId.ts @@ -0,0 +1,124 @@ +import { assert, hexToU8a, u8aToHex } from '@polkadot/util'; + +import { castToChainType, ChainType } from '@webb-tools/utils'; +import { BE } from './index'; + +/** + * Resource ID is an identifier of a resource in the Webb system. + * A resource is any piece of code / logic that maintains state and + * has its state updated over time. + * + * We use the resource ID to identify the resource in proposals that + * is being updated or being referenced in an update to another resource. + */ +export interface IResourceId { + /** + * The target system of the resource ID. This represents currently + * a 26-byte buffer representing a contract address or pallet and tree indicator. + * @example '0x000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + * @example '0x0000000000000000000000000000000000000000000000002301' + */ + readonly targetSystem: Uint8Array; + + /** + * 2 bytes (u16) encoded as the last 2 bytes of the resource id **just** before the chainId. + * + * **Note**: this value is optional here since we can read it from the `ResourceID`, but would be provided for you if + * you want to decode the proposal header from bytes. + */ + readonly chainType: ChainType; + + /** + * 4 bytes number (u32) of the `chainId` this also encoded in the last 4 bytes of the `ResourceID`. + * + * **Note**: this value is optional here since we can read it from the `ResourceID`, but would be provided for you if + * you want to decode the proposal header from bytes. + */ + readonly chainId: number; + + toU8a(): Uint8Array; + toString(): string; +} + +export class ResourceId implements IResourceId { + targetSystem: Uint8Array; + chainType: ChainType; + chainId: number; + + constructor(targetSystem: string | Uint8Array, chainType: ChainType, chainId: number) { + if (typeof targetSystem === 'string') { + const temp = hexToU8a(targetSystem); + + if (temp.length < 26) { + this.targetSystem = new Uint8Array(26); + this.targetSystem.set(temp, 6); + } else { + this.targetSystem = temp; + } + } else { + this.targetSystem = targetSystem; + } + + assert(this.targetSystem.length === 26, 'targetSystem must be 26 bytes'); + this.chainType = chainType; + this.chainId = chainId; + } + + /** + * Parses a `ResourceId` from a 32-byte Uint8Array. + */ + static fromBytes(bytes: Uint8Array): ResourceId { + assert(bytes.length === 32, 'bytes must be 32 bytes'); + + const targetSystem = bytes.slice(0, 26); + + const chainTypeInt = new DataView(bytes.buffer).getUint16(32 - 6, BE); + const chainType = castToChainType(chainTypeInt); + const chainId = new DataView(bytes.buffer).getUint32(32 - 4, BE); + + return new ResourceId(targetSystem, chainType, chainId); + } + + static newFromContractAddress( + contractAddress: string, + chainType: ChainType, + chainId: number + ): ResourceId { + assert( + (contractAddress.length === 42 && contractAddress.includes('0x')) || + (contractAddress.length === 40 && !contractAddress.includes('0x')), + 'contractAddress must be 42 bytes' + ); + + const targetSystem = new Uint8Array(26); + + // 6 -> 26 + targetSystem.set(hexToU8a(contractAddress), 6); + + return new ResourceId(targetSystem, chainType, chainId); + } + + /** + * Converts the resource ID into a 32-byte Uint8Array. + */ + toU8a(): Uint8Array { + const resourceId = new Uint8Array(32); + + resourceId.set(this.targetSystem, 0); + const view = new DataView(resourceId.buffer); + + // 26 -> 28 + view.setUint16(26, this.chainType, BE); + // 28 -> 32 + view.setUint32(28, this.chainId, BE); + + return resourceId; + } + + /** + * Converts the resource ID into a 32-byte Hex-encoded string. + */ + toString(): string { + return u8aToHex(this.toU8a()); + } +} diff --git a/packages/proposals/src/__test__/proposals.spec.ts b/packages/proposals/src/__test__/proposals.spec.ts new file mode 100644 index 000000000..c412ed34d --- /dev/null +++ b/packages/proposals/src/__test__/proposals.spec.ts @@ -0,0 +1,382 @@ +// Copyright 2022-2023 Webb Technologies Inc. +// SPDX-License-Identifier: Apache-2.0 + +import { assert } from 'chai'; +import { utils } from 'ethers'; + +import { hexToU8a, u8aToHex } from '@polkadot/util'; + +import { + EVMProposal, + FeeRecipientUpdateProposal, + MaxDepositLimitProposal, + MinWithdrawalLimitProposal, + RefreshProposal, + RegisterFungibleTokenProposal, + RegisterNftTokenProposal, + RescueTokensProposal, + ResourceIdUpdateProposal, + SetTreasuryHandlerProposal, + SetVerifierProposal, + TokenAddProposal, + TokenRemoveProposal, + WrappingFeeUpdateProposal, +} from '../'; +import { ProposalHeader } from '../ProposalHeader'; +import { AnchorUpdateProposal } from '../ProposalKinds'; +import { ResourceId } from '../ResourceId'; +import { ChainType } from '@webb-tools/utils'; + +describe('test various conversion functions', () => { + it('should encode and decode anchor update proposal types correctly', () => { + const anchorAddress = '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; + const chainId = 0xcafe; + const chainType = ChainType.EVM; + const resourceId = new ResourceId(anchorAddress, chainType, chainId); + const functionSignature = hexToU8a('0xdeadbeef'); + const lastLeafIndex = 0x0000feed; + const header = new ProposalHeader(resourceId, functionSignature, lastLeafIndex); + const srcChainId = 0xbabe; + const merkleRoot = '0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc'; + const otherAnchorAddress = '0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'; + const srcResourceId = new ResourceId(otherAnchorAddress, chainType, srcChainId); + const updateProposal = new AnchorUpdateProposal(header, merkleRoot, srcResourceId); + const headerEncoded = header.toU8a(); + const headerDecoded = ProposalHeader.fromBytes(headerEncoded); + + assert.equal(headerDecoded.resourceId.toString(), resourceId.toString()); + assert.equal(u8aToHex(headerDecoded.functionSignature), u8aToHex(functionSignature)); + assert.equal(headerDecoded.nonce, lastLeafIndex); + + const updateProposalEncoded = updateProposal.toU8a(); + const updateProposalDecoded = AnchorUpdateProposal.fromBytes(updateProposalEncoded); + + assert.equal(updateProposalDecoded.header.resourceId.toString(), resourceId.toString()); + assert.equal( + u8aToHex(updateProposalDecoded.header.functionSignature), + u8aToHex(functionSignature) + ); + assert.equal(updateProposalDecoded.header.nonce, lastLeafIndex); + assert.equal(updateProposalDecoded.merkleRoot, merkleRoot); + assert.equal(updateProposalDecoded.srcResourceId.toString(), srcResourceId.toString()); + }); + + it('Should encode and decode evm proposal', () => { + const tx = utils.parseTransaction( + '0x02f901fb018265708414077824850f609e3a0a83012c6f94f4c62b4f8b7b1b1c4ba88bfd3a8ea392641516e98726f8e5ab97bbd5b90184e3a54629000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000050000000000000000000000003696ce3b3d62326bc54ee471b329df7c60d94c2900000000000000000000000000000000000000000000000000091a706b2d1f7e0000000000000000000000002bdf24d26391195668acdf429c26c605b72029700000000000000000000000000000000000000000000000000007c575c8277aaf0000000000000000000000008da6869b882f27a0624e8a6736ef77ade0124adb0000000000000000000000000000000000000000000000000008bbcfdd814df50000000000000000000000006d4d6a996a670f80751f52c9c121710c06512a230000000000000000000000000000000000000000000000000008bc4ae2731e45000000000000000000000000214872c00ef77571916bf6773ab017cb5623b1410000000000000000000000000000000000000000000000000004a0e4b84eb56ec080a04f866699aaaefd51ca25e1202b7c7326184743888c191d2a13a16de013da2f84a07ac203cc7e5a0c7a390f6115be844d85db6892e681a3a7b4eb0f028909802548' + ); + + const evmProposal = new EVMProposal(tx.chainId, tx.nonce, tx); + const eVMProposalEncoded = evmProposal.toU8a(); + const eVMProposalDecoded = EVMProposal.fromBytes(eVMProposalEncoded); + + assert.equal(eVMProposalDecoded.nonce, tx.nonce); + assert.equal(eVMProposalDecoded.chainId, tx.chainId); + }); + + it('Should encode and decode refresh vote proposal', () => { + const merkleRoot = '0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc'; + const averageSessionLength = BigInt(10); + const voterCount = 3; + const publicKey = '0x020258d309d321e1108e1f055100b86df5d104ca589c1349e5731ef82b19ade12b'; + const nonce = 0; + + const refreshProposal = new RefreshProposal( + merkleRoot, + averageSessionLength, + voterCount, + nonce, + publicKey + ); + const refreshProposalEncoded = refreshProposal.toU8a(); + const refreshProposalDecoded = RefreshProposal.fromBytes(refreshProposalEncoded); + + assert.equal(refreshProposalDecoded.nonce, nonce); + assert.equal(refreshProposalDecoded.publicKey, publicKey); + }); + + it('Should encode and decode token add proposal', () => { + const anchorAddress = '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; + const newTokenAddress = '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab'; + const chainId = 0xcafe; + const chainType = ChainType.EVM; + const resourceId = new ResourceId(anchorAddress, chainType, chainId); + const functionSignature = hexToU8a('0xdeadbeef'); + const lastLeafIndex = 0x0000feed; + const header = new ProposalHeader(resourceId, functionSignature, lastLeafIndex); + + const tokenAddProposal = new TokenAddProposal(header, newTokenAddress); + const tokenAddProposalEncoded = tokenAddProposal.toU8a(); + const tokenAddProposalDecoded = TokenAddProposal.fromBytes(tokenAddProposalEncoded); + + assert.equal(tokenAddProposalDecoded.newTokenAddress, newTokenAddress); + assert.equal(tokenAddProposalDecoded.header.toString(), header.toString()); + }); + + it('Should encode and decode token remove proposal', () => { + const anchorAddress = '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; + const removedTokenAddress = '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab'; + const chainId = 0xcafe; + const chainType = ChainType.EVM; + const resourceId = new ResourceId(anchorAddress, chainType, chainId); + const functionSignature = hexToU8a('0xdeadbeef'); + const lastLeafIndex = 0x0000feed; + const header = new ProposalHeader(resourceId, functionSignature, lastLeafIndex); + + const tokenRemoveProposal = new TokenRemoveProposal(header, removedTokenAddress); + const tokenRemoveProposalEncoded = tokenRemoveProposal.toU8a(); + const tokenRemoveProposalDecoded = TokenRemoveProposal.fromBytes(tokenRemoveProposalEncoded); + + assert.equal(tokenRemoveProposalDecoded.removeTokenAddress, removedTokenAddress); + assert.equal(tokenRemoveProposalDecoded.header.toString(), header.toString()); + }); + + it('Should encode and decode wrapping fee update proposal', () => { + const anchorAddress = '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; + const newFee = '0x1011'; + const chainId = 0xcafe; + const chainType = ChainType.EVM; + const resourceId = new ResourceId(anchorAddress, chainType, chainId); + const functionSignature = hexToU8a('0xdeadbeef'); + const lastLeafIndex = 0x0000feed; + const header = new ProposalHeader(resourceId, functionSignature, lastLeafIndex); + + const wrappingFeeUpdateProposal = new WrappingFeeUpdateProposal(header, newFee); + const wrappingFeeUpdateProposalEncoded = wrappingFeeUpdateProposal.toU8a(); + const wrappingFeeUpdateProposalDecoded = WrappingFeeUpdateProposal.fromBytes( + wrappingFeeUpdateProposalEncoded + ); + + assert.equal(wrappingFeeUpdateProposalDecoded.newFee, newFee); + assert.equal(wrappingFeeUpdateProposalDecoded.header.toString(), header.toString()); + }); + + it('Should encode and decode min withdraw limit proposal', () => { + const anchorAddress = '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; + const minWithdrawalLimitBytes = + '0x0000000000000000000000000000000000000000000000000000000000001111'; + const chainId = 0xcafe; + const chainType = ChainType.EVM; + const resourceId = new ResourceId(anchorAddress, chainType, chainId); + const functionSignature = hexToU8a('0xdeadbeef'); + const lastLeafIndex = 0x0000feed; + const header = new ProposalHeader(resourceId, functionSignature, lastLeafIndex); + + const minWithdrawLimitProposal = new MinWithdrawalLimitProposal( + header, + minWithdrawalLimitBytes + ); + const minWithdrawLimitProposalEncoded = minWithdrawLimitProposal.toU8a(); + const minWithdrawLimitProposalDecoded = MinWithdrawalLimitProposal.fromBytes( + minWithdrawLimitProposalEncoded + ); + + assert.equal(minWithdrawLimitProposalDecoded.minWithdrawalLimitBytes, minWithdrawalLimitBytes); + assert.equal(minWithdrawLimitProposalDecoded.header.toString(), header.toString()); + }); + + it('Should encode and decode max deposit limit proposal', () => { + const anchorAddress = '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; + const maxDepositLimitBytes = + '0x0000000000000000000000000000000000000000000000000000000000001111'; + const chainId = 0xcafe; + const chainType = ChainType.EVM; + const resourceId = new ResourceId(anchorAddress, chainType, chainId); + const functionSignature = hexToU8a('0xdeadbeef'); + const lastLeafIndex = 0x0000feed; + const header = new ProposalHeader(resourceId, functionSignature, lastLeafIndex); + + const maxDepositLimitProposal = new MaxDepositLimitProposal(header, maxDepositLimitBytes); + const maxDepositLimitProposalEncoded = maxDepositLimitProposal.toU8a(); + const maxDepositLimitProposalDecoded = MaxDepositLimitProposal.fromBytes( + maxDepositLimitProposalEncoded + ); + + assert.equal(maxDepositLimitProposalDecoded.maxDepositLimitBytes, maxDepositLimitBytes); + assert.equal(maxDepositLimitProposalDecoded.header.toString(), header.toString()); + }); + + it('Should encode and decode resourceId update proposal', () => { + const anchorAddress = '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; + const handlerAddress = '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabb'; + const chainId = 0xcafe; + const chainId2 = 0xcafa; + const chainType = ChainType.EVM; + const resourceId = new ResourceId(anchorAddress, chainType, chainId); + const newResourceId = new ResourceId(anchorAddress, chainType, chainId2); + const functionSignature = hexToU8a('0xdeadbeef'); + const lastLeafIndex = 0x0000feed; + const header = new ProposalHeader(resourceId, functionSignature, lastLeafIndex); + + const resourceIdUpdateProposal = new ResourceIdUpdateProposal( + header, + newResourceId.toString(), + handlerAddress + ); + const resourceIdUpdateProposalEncoded = resourceIdUpdateProposal.toU8a(); + const resourceIdUpdateProposalDecoded = ResourceIdUpdateProposal.fromBytes( + resourceIdUpdateProposalEncoded + ); + + assert.equal(resourceIdUpdateProposalDecoded.handlerAddress, handlerAddress); + assert.equal(resourceIdUpdateProposalDecoded.newResourceId, newResourceId.toString()); + assert.equal(resourceIdUpdateProposalDecoded.header.toString(), header.toString()); + }); + + it('Should encode and decode set treasury handler proposal', () => { + const anchorAddress = '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; + const newTreasuryHandler = '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabb'; + const chainId = 0xcafe; + const chainType = ChainType.EVM; + const resourceId = new ResourceId(anchorAddress, chainType, chainId); + const functionSignature = hexToU8a('0xdeadbeef'); + const lastLeafIndex = 0x0000feed; + const header = new ProposalHeader(resourceId, functionSignature, lastLeafIndex); + + const setTreasuryHandlerProposal = new SetTreasuryHandlerProposal(header, newTreasuryHandler); + const setTreasuryHandlerProposalEncoded = setTreasuryHandlerProposal.toU8a(); + const setTreasuryHandlerProposalDecoded = SetTreasuryHandlerProposal.fromBytes( + setTreasuryHandlerProposalEncoded + ); + + assert.equal(setTreasuryHandlerProposalDecoded.newTreasuryHandler, newTreasuryHandler); + assert.equal(setTreasuryHandlerProposalDecoded.header.toString(), header.toString()); + }); + + it('Should encode and decode set verifier proposal', () => { + const anchorAddress = '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; + const newVerifier = '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabb'; + const chainId = 0xcafe; + const chainType = ChainType.EVM; + const resourceId = new ResourceId(anchorAddress, chainType, chainId); + const functionSignature = hexToU8a('0xdeadbeef'); + const lastLeafIndex = 0x0000feed; + const header = new ProposalHeader(resourceId, functionSignature, lastLeafIndex); + + const setVerifierProposal = new SetVerifierProposal(header, newVerifier); + const setVerifierProposalEncoded = setVerifierProposal.toU8a(); + const setVerifierProposalDecoded = SetVerifierProposal.fromBytes(setVerifierProposalEncoded); + + assert.equal(setVerifierProposalDecoded.newVerifier, newVerifier); + assert.equal(setVerifierProposalDecoded.header.toString(), header.toString()); + }); + + it('Should encode and decode fee recipient update proposal', () => { + const anchorAddress = '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; + const newFeeRecipient = '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabb'; + const chainId = 0xcafe; + const chainType = ChainType.EVM; + const resourceId = new ResourceId(anchorAddress, chainType, chainId); + const functionSignature = hexToU8a('0xdeadbeef'); + const lastLeafIndex = 0x0000feed; + const header = new ProposalHeader(resourceId, functionSignature, lastLeafIndex); + + const feeRecipientUpdateProposal = new FeeRecipientUpdateProposal(header, newFeeRecipient); + const feeRecipientUpdateProposalEncoded = feeRecipientUpdateProposal.toU8a(); + const feeRecipientUpdateProposalDecoded = FeeRecipientUpdateProposal.fromBytes( + feeRecipientUpdateProposalEncoded + ); + + assert.equal(feeRecipientUpdateProposalDecoded.newFeeRecipient, newFeeRecipient); + assert.equal(feeRecipientUpdateProposalDecoded.header.toString(), header.toString()); + }); + + it('Should encode and decode rescue token proposal', () => { + const anchorAddress = '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; + const tokenAddress = '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabb'; + const toAddress = '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacc'; + const amount = '0x0000000000000000000000000000000000000000000000000000000000001111'; + const chainId = 0xcafe; + const chainType = ChainType.EVM; + const resourceId = new ResourceId(anchorAddress, chainType, chainId); + const functionSignature = hexToU8a('0xdeadbeef'); + const lastLeafIndex = 0x0000feed; + const header = new ProposalHeader(resourceId, functionSignature, lastLeafIndex); + + const feeRecipientUpdateProposal = new RescueTokensProposal( + header, + tokenAddress, + toAddress, + amount + ); + const feeRecipientUpdateProposalEncoded = feeRecipientUpdateProposal.toU8a(); + const feeRecipientUpdateProposalDecoded = RescueTokensProposal.fromBytes( + feeRecipientUpdateProposalEncoded + ); + + assert.equal(feeRecipientUpdateProposalDecoded.tokenAddress, tokenAddress); + assert.equal(feeRecipientUpdateProposalDecoded.toAddress, toAddress); + assert.equal(feeRecipientUpdateProposalDecoded.amount, amount); + assert.equal(feeRecipientUpdateProposalDecoded.header.toString(), header.toString()); + }); + + it('Should encode and decode register fungible token proposal', () => { + const anchorAddress = '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; + const chainId = 0xcafe; + const chainType = ChainType.EVM; + const resourceId = new ResourceId(anchorAddress, chainType, chainId); + const functionSignature = hexToU8a('0xdeadbeef'); + const nonce = 0x0000feed; + const header = new ProposalHeader(resourceId, functionSignature, nonce); + + const tokenHandler = '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; + const assetId = '0xbbbbbbbb'; + const name = '0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc'; + const symbol = '0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc'; + + const registerFungibleTokenProposal = new RegisterFungibleTokenProposal( + header, + tokenHandler, + assetId, + name, + symbol + ); + const registerFungibleTokenProposalEncoded = registerFungibleTokenProposal.toU8a(); + const registerFungibleTokenProposalDecoded = RegisterFungibleTokenProposal.fromBytes( + registerFungibleTokenProposalEncoded + ); + + assert.equal(registerFungibleTokenProposalDecoded.header.toString(), header.toString()); + assert.equal(registerFungibleTokenProposalDecoded.tokenHandler, tokenHandler); + assert.equal(registerFungibleTokenProposalDecoded.assetId, assetId); + assert.equal(registerFungibleTokenProposalDecoded.name, name); + assert.equal(registerFungibleTokenProposalDecoded.symbol, symbol); + }); + + it('Should encode and decode register nft token proposal', () => { + const anchorAddress = '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; + const chainId = 0xcafe; + const chainType = ChainType.EVM; + const resourceId = new ResourceId(anchorAddress, chainType, chainId); + const functionSignature = hexToU8a('0xdeadbeef'); + const nonce = 0x0000feed; + const header = new ProposalHeader(resourceId, functionSignature, nonce); + + const tokenHandler = '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; + const assetId = '0xbbbbbbbb'; + const collectionAddress = '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; + const salt = '0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc'; + const uri = + '0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc'; + + const registerNftTokenProposal = new RegisterNftTokenProposal( + header, + tokenHandler, + assetId, + collectionAddress, + salt, + uri + ); + const registerNftTokenProposalEncoded = registerNftTokenProposal.toU8a(); + const registerNftTokenProposalDecoded = RegisterNftTokenProposal.fromBytes( + registerNftTokenProposalEncoded + ); + + assert.equal(registerNftTokenProposalDecoded.header.toString(), header.toString()); + assert.equal(registerNftTokenProposalDecoded.tokenHandler, tokenHandler); + assert.equal(registerNftTokenProposalDecoded.assetId, assetId); + assert.equal(registerNftTokenProposalDecoded.collectionAddress, collectionAddress); + assert.equal(registerNftTokenProposalDecoded.salt, salt); + assert.equal(registerNftTokenProposalDecoded.uri, uri); + }); +}); diff --git a/packages/proposals/src/index.ts b/packages/proposals/src/index.ts new file mode 100644 index 000000000..83c7b3ba9 --- /dev/null +++ b/packages/proposals/src/index.ts @@ -0,0 +1,27 @@ +/* + * Copyright 2022 Webb Technologies Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +// Flags used to indicate the type of encoding to be used +// in various encoding functions. +// +// For example: `const nonce = new DataView(bytes.buffer).getUint32(36, BE);` +export const LE = true; +export const BE = false; + +export * from './ProposalHeader'; +export * from './ResourceId'; +export * from './ProposalKinds'; diff --git a/packages/proposals/tsconfig.build.json b/packages/proposals/tsconfig.build.json new file mode 100644 index 000000000..48006d517 --- /dev/null +++ b/packages/proposals/tsconfig.build.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "rootDir": "./src/", + "outDir": "./dist/", + "baseUrl": "." + }, + "exclude": ["./src/**/*.spec.ts"], + "extends": "../../tsconfig.build.json", + "include": [ + "./src/**/*.ts", + ] +} diff --git a/packages/proposals/tsconfig.json b/packages/proposals/tsconfig.json new file mode 100644 index 000000000..7460ef428 --- /dev/null +++ b/packages/proposals/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.json" +} \ No newline at end of file diff --git a/packages/tokens/package.json b/packages/tokens/package.json index ee6c2153f..de3ef2e52 100644 --- a/packages/tokens/package.json +++ b/packages/tokens/package.json @@ -11,7 +11,6 @@ "dependencies": { "@webb-tools/contracts": "^1.0.4", "@webb-tools/create2-utils": "^1.0.4", - "@webb-tools/sdk-core": "0.1.4-126", "@webb-tools/utils": "^1.0.4", "ethers": "5.7.0" }, diff --git a/packages/tokens/src/FungibleTokenWrapper.ts b/packages/tokens/src/FungibleTokenWrapper.ts index f37f1c7f8..0b2318079 100644 --- a/packages/tokens/src/FungibleTokenWrapper.ts +++ b/packages/tokens/src/FungibleTokenWrapper.ts @@ -1,6 +1,6 @@ import { BigNumberish, ethers } from 'ethers'; import { getChainIdType } from '@webb-tools/utils'; -import { toHex, generateFunctionSigHash } from '@webb-tools/sdk-core'; +import { toHex, generateFunctionSigHash } from '@webb-tools/utils'; import { FungibleTokenWrapper as FungibleTokenWrapperContract, FungibleTokenWrapper__factory, diff --git a/packages/tokens/src/Treasury.ts b/packages/tokens/src/Treasury.ts index 7779e07a8..0ac808d4e 100644 --- a/packages/tokens/src/Treasury.ts +++ b/packages/tokens/src/Treasury.ts @@ -1,6 +1,6 @@ import { BigNumber, ethers } from 'ethers'; import { getChainIdType } from '@webb-tools/utils'; -import { toHex, generateFunctionSigHash, toFixedHex } from '@webb-tools/sdk-core'; +import { toHex, generateFunctionSigHash, toFixedHex } from '@webb-tools/utils'; import { Treasury as TreasuryContract, Treasury__factory } from '@webb-tools/contracts'; import { Deployer } from '@webb-tools/create2-utils'; diff --git a/packages/utils/.mocharc.json b/packages/utils/.mocharc.json new file mode 100644 index 000000000..51d7317fc --- /dev/null +++ b/packages/utils/.mocharc.json @@ -0,0 +1,5 @@ +{ + "require": "ts-node/register", + "extensions": ["ts"], + "spec": ["**/*.spec.*"] +} \ No newline at end of file diff --git a/packages/utils/package.json b/packages/utils/package.json index bd5652346..37e661e41 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,16 +1,18 @@ { "name": "@webb-tools/utils", "main": "./dist/index.js", + "types": "./dist/index.d.ts", "license": "(MIT OR Apache-2.0)", "author": "Webb Developers ", "scripts": { "build": "yarn run clean && yarn run compile", "clean": "rimraf -rf ./dist", - "compile": "tsc -p tsconfig.build.json" + "compile": "tsc -p tsconfig.build.json", + "test": "mocha -r ts-node/register 'src/__test__/**/*.ts'" }, "dependencies": { - "@webb-tools/sdk-core": "0.1.4-126", - "babyjubjub": "^1.0.4", + "@metamask/eth-sig-util": "^6.0.0", + "circomlibjs": "^0.0.8", "ethers": "5.7.0", "snarkjs": "^0.6.10" }, @@ -23,5 +25,9 @@ "url": "git://github.com/webb-tools/protocol-solidity.git" }, "version": "1.0.4", - "gitHead": "e1f3b300b6e004ac5a346dc0458bb1d303969d97" + "gitHead": "e1f3b300b6e004ac5a346dc0458bb1d303969d97", + "devDependencies": { + "@webb-tools/sdk-core": "0.1.4-126", + "chai": "^4.3.7" + } } diff --git a/packages/utils/src/__test__/keypair.spec.ts b/packages/utils/src/__test__/keypair.spec.ts new file mode 100644 index 000000000..be59d54bc --- /dev/null +++ b/packages/utils/src/__test__/keypair.spec.ts @@ -0,0 +1,50 @@ +import { getEncryptionPublicKey } from '@metamask/eth-sig-util'; +import assert from 'assert'; +import { poseidon } from 'circomlibjs'; +import { Keypair } from '../protocol/keypair'; +import { toFixedHex } from '../utils'; + +describe('Keypair constructor tests', () => { + it('Should create a Keypair when using the constructor with a private key', async function () { + const testPrivateKey = '0x0000000000000000000000000000000000000000000000000000000000000001'; + const keypair = new Keypair(testPrivateKey); + + const expectedPubkey = poseidon([testPrivateKey]); + const expectedEncryptionKey = + '0x' + Buffer.from(getEncryptionPublicKey(testPrivateKey.slice(2)), 'base64').toString('hex'); + + assert(keypair.getPubKey() === toFixedHex(expectedPubkey)); + assert(keypair.getEncryptionKey() === expectedEncryptionKey); + + const encryptedValue = keypair.encrypt(Buffer.from('hello')); + const decryptedValue = keypair.decrypt(encryptedValue); + + assert(Buffer.from('hello').toString() === decryptedValue.toString()); + }); + + it('Should create a Keypair consisting of just the public key', async function () { + const testPublicKey = '0x0000000000000000000000000000000000000000000000000000000000000001'; + const keypair = Keypair.fromString(testPublicKey); + + assert(keypair.getPubKey() === toFixedHex(testPublicKey)); + assert(keypair.privkey === undefined); + assert(keypair.getEncryptionKey() === undefined); + assert.throws(() => keypair.encrypt(Buffer.from('hi'))); + }); + + it('Should create a Keypair consisting of public key and encryption key', async function () { + const testPublicKey = '0x0000000000000000000000000000000000000000000000000000000000000001'; + const testEncryptionKey = '0x0000000000000000000000000000000000000000000000000000000000000002'; + const testPublicData = testPublicKey + testEncryptionKey.slice(2); + const keypair = Keypair.fromString(testPublicData); + + assert(keypair.getPubKey() === toFixedHex(testPublicKey), 'public keys not equal'); + assert(keypair.toString() === testPublicData, 'serialize and deserialize the same values'); + assert(keypair.getEncryptionKey() === testEncryptionKey, 'encryption keys not equal'); + // make a call to encrypt and ensure the function does not throw. + const encryptedValue = keypair.encrypt(Buffer.from('hello')); + + // make a call to decrypt and ensure the function throws + assert.throws(() => keypair.decrypt(encryptedValue)); + }); +}); diff --git a/packages/utils/src/__test__/merkle-tree.spec.ts b/packages/utils/src/__test__/merkle-tree.spec.ts new file mode 100644 index 000000000..c32c6c006 --- /dev/null +++ b/packages/utils/src/__test__/merkle-tree.spec.ts @@ -0,0 +1,124 @@ +// Copyright 2022-2023 Webb Technologies Inc. +// SPDX-License-Identifier: Apache-2.0 + +import { expect } from 'chai'; +import { MerkleTree } from '../protocol/merkle-tree'; + +describe('Merkle Tree tests', () => { + const elements = [12, 13, 14, 15, 16, 17, 18, 19, 20]; + + describe('construction tests', () => { + it('should evaluate the same for constructor with elements and constructor with insertions', () => { + const treeWithElements = new MerkleTree(15, elements); + + const treeThenInsert = new MerkleTree(15); + + for (const element of elements) { + treeThenInsert.insert(element); + } + + expect(treeThenInsert.root().toHexString()).to.eq(treeWithElements.root().toHexString()); + }); + }); + + describe('insertion tests', () => { + it('should evaluate the same for bulkInsert and single insert', () => { + const singleTree = new MerkleTree(6); + const bulkTree = new MerkleTree(6); + + bulkTree.bulkInsert(elements); + + for (const el of elements) { + singleTree.insert(el); + } + + for (let i = 0; i < elements.length; i++) { + const bulkPath = bulkTree.path(i); + const singlePath = singleTree.path(i); + + expect(bulkPath.merkleRoot.toHexString()).to.eq(singlePath.merkleRoot.toHexString()); + expect(bulkPath.element.toHexString()).to.eq(singlePath.element.toHexString()); + expect(bulkPath.pathIndices).to.eql(singlePath.pathIndices); + expect(bulkPath.pathElements).to.eql(singlePath.pathElements); + } + }); + + it('should find an element', async () => { + const tree = new MerkleTree(20); + + tree.bulkInsert(elements); + let index = tree.getIndexByElement(13); + + expect(index).to.eq(1); + + index = tree.getIndexByElement(19); + expect(index).to.eq(7); + + index = tree.getIndexByElement(12); + expect(index).to.eq(0); + + index = tree.getIndexByElement(20); + expect(index).to.eq(8); + + index = tree.getIndexByElement(42); + expect(index).to.eq(-1); + }); + }); + + describe('removal tests', () => { + let singleTree: MerkleTree; + let bulkTree: MerkleTree; + let initialRoot: any; // BigNumber + + before(async () => { + singleTree = new MerkleTree(6); + bulkTree = new MerkleTree(6); + initialRoot = singleTree.root(); + + bulkTree.bulkInsert(elements); + + for (const el of elements) { + singleTree.insert(el); + } + + for (let i = 0; i < elements.length; i++) { + const bulkPath = bulkTree.path(i); + const singlePath = singleTree.path(i); + + expect(bulkPath.merkleRoot.toHexString()).to.eq(singlePath.merkleRoot.toHexString()); + expect(bulkPath.element.toHexString()).to.eq(singlePath.element.toHexString()); + expect(bulkPath.pathIndices).to.eql(singlePath.pathIndices); + expect(bulkPath.pathElements).to.eql(singlePath.pathElements); + } + }); + + it('should evaluate the same for removeBulk and single remove', () => { + bulkTree.bulkRemove(elements); + + for (const el of elements) { + singleTree.remove(el); + } + + for (let i = 0; i < elements.length; i++) { + const bulkPath = bulkTree.path(i); + const singlePath = singleTree.path(i); + + expect(bulkPath.merkleRoot.toHexString()).to.eq(singlePath.merkleRoot.toHexString()); + expect(bulkPath.element.toHexString()).to.eq(singlePath.element.toHexString()); + expect(bulkPath.pathIndices).to.eql(singlePath.pathIndices); + expect(bulkPath.pathElements).to.eql(singlePath.pathElements); + } + + expect(bulkTree.root().toHexString()).to.eq(singleTree.root().toHexString()); + // checking if root matches root without any elements as all of them have been removed + expect(bulkTree.root().toHexString()).to.eq(initialRoot.toHexString()); + }); + }); + + it('should correctly calculate the index from pathIndices', () => { + const pathIndices = [0, 1, 1, 0, 1]; + const calculatedIndex = MerkleTree.calculateIndexFromPathIndices(pathIndices); + + expect(calculatedIndex).to.eq(22); + }); +}); diff --git a/packages/utils/src/__test__/note.spec.ts b/packages/utils/src/__test__/note.spec.ts new file mode 100644 index 000000000..09dd952ad --- /dev/null +++ b/packages/utils/src/__test__/note.spec.ts @@ -0,0 +1,521 @@ +// Copyright 2022-2023 Webb Technologies Inc. +// SPDX-License-Identifier: Apache-2.0 + +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +import { expect } from 'chai'; + +import { Note, NoteGenInput } from '../protocol/note'; + +describe('Note class', () => { + it('should test constructor from `NoteGenInput`', async () => { + const noteInput: NoteGenInput = { + amount: '1', + backend: 'Arkworks', + curve: 'Bn254', + denomination: '18', + exponentiation: '5', + hashFunction: 'Poseidon', + protocol: 'vanchor', + sourceChain: '1', + sourceIdentifyingData: '1', + targetChain: '1', + targetIdentifyingData: '1', + tokenSymbol: 'WEBB', + version: 'v1', + width: '5', + }; + + const note = Note.generateNote(noteInput); + expect(note.amount).to.deep.equal('1'); + expect(note.denomination).to.deep.equal('18'); + expect(note.width).to.deep.equal('5'); + expect(note.exponentiation).to.deep.equal('5'); + expect(note.targetChainId).to.deep.equal('1'); + expect(note.targetIdentifyingData).to.deep.equal('1'); + expect(note.sourceChainId).to.deep.equal('1'); + expect(note.sourceIdentifyingData).to.deep.equal('1'); + expect(note.backend).to.deep.equal('Arkworks'); + expect(note.hashFunction).to.deep.equal('Poseidon'); + expect(note.curve).to.deep.equal('Bn254'); + expect(note.tokenSymbol).to.deep.equal('WEBB'); + }); + + it('should test serializing and deserializing', async () => { + const noteInput: NoteGenInput = { + amount: '1', + backend: 'Arkworks', + curve: 'Bn254', + denomination: '18', + exponentiation: '5', + hashFunction: 'Poseidon', + protocol: 'vanchor', + sourceChain: '1', + sourceIdentifyingData: '1', + targetChain: '1', + targetIdentifyingData: '1', + tokenSymbol: 'WEBB', + version: 'v1', + width: '5', + }; + + const note = Note.generateNote(noteInput); + const serializedNote = note.serialize(); + const deserializedNote = Note.deserialize(serializedNote); + expect(deserializedNote.sourceChainId).to.deep.equal('1'); + expect(deserializedNote.sourceIdentifyingData).to.deep.equal('1'); + expect(deserializedNote.targetChainId).to.deep.equal('1'); + expect(deserializedNote.targetIdentifyingData).to.deep.equal('1'); + expect(deserializedNote.backend).to.deep.equal('Arkworks'); + expect(deserializedNote.hashFunction).to.deep.equal('Poseidon'); + expect(deserializedNote.curve).to.deep.equal('Bn254'); + expect(deserializedNote.tokenSymbol).to.deep.equal('WEBB'); + expect(deserializedNote.amount).to.deep.equal('1'); + expect(deserializedNote.denomination).to.deep.equal('18'); + expect(deserializedNote.width).to.deep.equal('5'); + expect(deserializedNote.exponentiation).to.deep.equal('5'); + }); + + it('should test vanchor secret destination chain', async () => { + const noteInput: NoteGenInput = { + amount: '1', + backend: 'Arkworks', + curve: 'Bn254', + denomination: '18', + exponentiation: '5', + hashFunction: 'Poseidon', + protocol: 'vanchor', + sourceChain: '1', + sourceIdentifyingData: '1', + targetChain: '1', + targetIdentifyingData: '1', + tokenSymbol: 'WEBB', + version: 'v1', + width: '5', + }; + + const note = Note.generateNote(noteInput); + const targetChainFromSecrets = note.secrets[0]; + const targetChainBuffer = Buffer.from(targetChainFromSecrets, 'hex'); + const targetChain = targetChainBuffer.readBigUInt64BE(); + + expect(targetChain.toString()).to.deep.equal('1'); + const noteString = note.serialize(); + const noteDeserialized = Note.deserialize(noteString); + const targetChainFromDeserializedSecrets = noteDeserialized.secrets[0]; + const targetChainBufferDeserialized = Buffer.from(targetChainFromDeserializedSecrets, 'hex'); + const targetChainDeserialized = targetChainBufferDeserialized.readBigUInt64BE(); + + expect(targetChainDeserialized.toString()).to.deep.equal('1'); + }); + + it('should fail with circom backend without secrets', async () => { + const noteInput: NoteGenInput = { + amount: '1', + backend: 'Circom', + curve: 'Bn254', + denomination: '18', + exponentiation: '5', + hashFunction: 'Poseidon', + protocol: 'vanchor', + sourceChain: '1', + sourceIdentifyingData: '1', + targetChain: '1', + targetIdentifyingData: '1', + tokenSymbol: 'WEBB', + version: 'v1', + width: '5', + }; + + try { + Note.generateNote(noteInput); + } catch (e: any) { + expect(e.code).to.equal(42); + expect(e.message).to.equal('Circom backend is supported when the secret value is supplied'); + } + }); + + it('should generate a vanchor note with circom backend when secrets are passed', async () => { + const noteInput: NoteGenInput = { + amount: '1000000000000000000', + backend: 'Circom', + curve: 'Bn254', + denomination: '18', + exponentiation: '5', + hashFunction: 'Poseidon', + protocol: 'vanchor', + secrets: + '0000010000007a69:002b977c06e55a6b1fb0552373c9001a2d0601d2c739607954c1c291278b07ca:0033b53cd0cb680239a7b1a671282f30cbcaa9e6fa05c6b1bfd11ef7a1d1a1fa:002b977c06e55a6b1fb0552373c9001a2d0601d2c739607954c1c291278b07ca', + sourceChain: '1099511659113', + sourceIdentifyingData: '1', + targetChain: '1099511659113', + targetIdentifyingData: '1', + tokenSymbol: 'DAI', + version: 'v1', + width: '5', + }; + + const note = Note.generateNote(noteInput); + + expect(note.backend).to.equal('Circom'); + }); + + it('should fail to deserialize invalid protocol', async () => { + const serialized = + 'webb://' + + 'v1:invalid/' + + '1:1/' + + '1:1/' + + '0000000000000001:ae6c3f92db70334231435b03ca139970e2eeff43860171b9f20a0de4b423741e:339e6c9b0a571e612dbcf60e2c20fc58b4e037f00e9384f0f2c872feea91802b/' + + '?curve=Bn254&width=4&exp=5&hf=Poseidon&backend=Circom&token=WEBB&denom=18&amount=1'; + + try { + Note.deserialize(serialized); + } catch (e: any) { + expect(e.code).to.equal(4); + expect(e.message).to.equal('Invalid note protocol'); + } + }); + + it('should fail to deserialize invalid version', async () => { + const serialized = + 'webb://' + + 'v3:vanchor/' + + '1:1/' + + '1:1/' + + '0000000000000001:ae6c3f92db70334231435b03ca139970e2eeff43860171b9f20a0de4b423741e:339e6c9b0a571e612dbcf60e2c20fc58b4e037f00e9384f0f2c872feea91802b:339e6c9b0a571e612dbcf60e2c20fc58b4e037f00e9384f0f2c872feea91802b/' + + '?curve=Bn254&width=4&exp=5&hf=Poseidon&backend=Circom&token=WEBB&denom=18&amount=1'; + + try { + Note.deserialize(serialized); + } catch (e: any) { + expect(e.code).to.equal(5); + expect(e.message).to.equal('Invalid note version'); + } + }); + + it('should fail to deserialize invalid source chain id', async () => { + const serialized = + 'webb://' + + 'v1:vanchor/' + + 'invalid_source_chain_id:1/' + + '1:1/' + + '0000000000000001:ae6c3f92db70334231435b03ca139970e2eeff43860171b9f20a0de4b423741e:339e6c9b0a571e612dbcf60e2c20fc58b4e037f00e9384f0f2c872feea91802b:339e6c9b0a571e612dbcf60e2c20fc58b4e037f00e9384f0f2c872feea91802b/' + + '?curve=Bn254&width=4&exp=5&hf=Poseidon&backend=Circom&token=WEBB&denom=18&amount=1'; + + try { + Note.deserialize(serialized); + } catch (e: any) { + expect(e.code).to.equal(18); + expect(e.message).to.equal('Invalid source chain id'); + } + }); + + it('should fail to deserialize invalid target chain id', async () => { + const serialized = + 'webb://' + + 'v1:vanchor/' + + '1:invalid_target_chain_id/' + + '1:1/' + + '0000000000000001:ae6c3f92db70334231435b03ca139970e2eeff43860171b9f20a0de4b423741e:339e6c9b0a571e612dbcf60e2c20fc58b4e037f00e9384f0f2c872feea91802b:339e6c9b0a571e612dbcf60e2c20fc58b4e037f00e9384f0f2c872feea91802b/' + + '?curve=Bn254&width=4&exp=5&hf=Poseidon&backend=Circom&token=WEBB&denom=18&amount=1'; + + try { + Note.deserialize(serialized); + } catch (e: any) { + expect(e.code).to.equal(19); + expect(e.message).to.equal('Invalid target chain id'); + } + }); + + it('should fail to deserialize invalid note length', async () => { + const serialized = + 'webb://' + + 'v1:vanchor/' + + '1:1/' + + // + '1:1/' Nullify the source identify data + '0000000000000001:ae6c3f92db70334231435b03ca139970e2eeff43860171b9f20a0de4b423741e:339e6c9b0a571e612dbcf60e2c20fc58b4e037f00e9384f0f2c872feea91802b:339e6c9b0a571e612dbcf60e2c20fc58b4e037f00e9384f0f2c872feea91802b/' + + '?curve=Bn254&width=4&exp=5&hf=Poseidon&backend=Circom&token=WEBB&denom=18&amount=1'; + + try { + Note.deserialize(serialized); + } catch (e: any) { + expect(e.code).to.equal(3); + expect(e.message).to.equal('Note length has incorrect parts length: 4'); + } + }); + + it('should fail to deserialize anchor invalid secrets (invalid chain id item - too large)', async () => { + const serialized = + 'webb://' + + 'v1:vanchor/' + + '1:1/' + + '1:1/' + + // Get rid of target chain ID from :from secrets portion + '11010101010101100000000000000001:ae6c3f92db70334231435b03ca139970e2eeff43860171b9f20a0de4b423741e:339e6c9b0a571e612dbcf60e2c20fc58b4e037f00e9384f0f2c872feea91802b/' + + '?curve=Bn254&width=4&exp=5&hf=Poseidon&backend=Circom&token=WEBB&denom=18&amount=1'; + + try { + Note.deserialize(serialized); + } catch (e: any) { + expect(e.code).to.equal(8); + expect(e.message).to.equal('Invalid note secrets'); + } + }); + + it('should fail to deserialize anchor invalid secrets (invalid chain id item - too small)', async () => { + const serialized = + 'webb://' + + 'v1:vanchor/' + + '1:1/' + + '1:1/' + + // Get rid of target chain ID from :from secrets portion + '0001:ae6c3f92db70334231435b03ca139970e2eeff43860171b9f20a0de4b423741e:339e6c9b0a571e612dbcf60e2c20fc58b4e037f00e9384f0f2c872feea91802b/' + + '?curve=Bn254&width=4&exp=5&hf=Poseidon&backend=Circom&token=WEBB&denom=18&amount=1'; + + try { + Note.deserialize(serialized); + } catch (e: any) { + expect(e.code).to.equal(8); + expect(e.message).to.equal('Invalid note secrets'); + } + }); + + it('should fail to deserialize anchor invalid secrets (missing chain id item)', async () => { + const serialized = + 'webb://' + + 'v1:vanchor/' + + '1:1/' + + '1:1/' + + // Get rid of target chain ID from :from secrets portion + 'ae6c3f92db70334231435b03ca139970e2eeff43860171b9f20a0de4b423741e:339e6c9b0a571e612dbcf60e2c20fc58b4e037f00e9384f0f2c872feea91802b/' + + '?curve=Bn254&width=4&exp=5&hf=Poseidon&backend=Circom&token=WEBB&denom=18&amount=1'; + + try { + Note.deserialize(serialized); + } catch (e: any) { + expect(e.code).to.equal(3); + expect(e.message).to.equal('Invalid note length'); + } + }); + + it('should fail to deserialize anchor invalid secrets (nullifier item)', async () => { + const serialized = + 'webb://' + + 'v1:vanchor/' + + '1:1/' + + '1:1/' + + // Get rid of target chain ID from :from secrets portion + '0000000000000001:339e6c9b0a571e612dbcf60e2c20fc58b4e037f00e9384f0f2c872feea91802b/' + + '?curve=Bn254&width=4&exp=5&hf=Poseidon&backend=Circom&token=WEBB&denom=18&amount=1'; + + try { + Note.deserialize(serialized); + } catch (e: any) { + expect(e.code).to.equal(3); + expect(e.message).to.equal('Invalid note length'); + } + }); + + it('should fail to deserialize anchor invalid secrets (multiple colons)', async () => { + const serialized = + 'webb://' + + 'v1:vanchor/' + + '1:1/' + + '1:1/' + + // Remove a secret item and :and leave colon + '0000000000000001::339e6c9b0a571e612dbcf60e2c20fc58b4e037f00e9384f0f2c872feea91802b/' + + '?curve=Bn254&width=4&exp=5&hf=Poseidon&backend=Circom&token=WEBB&denom=18&amount=1'; + + try { + Note.deserialize(serialized); + } catch (e: any) { + expect(e.code).to.equal(8); + expect(e.message).to.equal('Invalid note secrets'); + } + }); + + it('should fail to deserialize anchor invalid secrets (1 colon)', async () => { + const serialized = + 'webb://' + + 'v1:vanchor/' + + '1:1/' + + '1:1/' + + // Remove a secret item and also :also remove colon + '0000000000000001:339e6c9b0a571e612dbcf60e2c20fc58b4e037f00e9384f0f2c872feea91802b/' + + '?curve=Bn254&width=4&exp=5&hf=Poseidon&backend=Circom&token=WEBB&denom=18&amount=1'; + + try { + Note.deserialize(serialized); + } catch (e: any) { + expect(e.code).to.equal(3); + expect(e.message).to.equal('Invalid note length'); + } + }); + + it('should deserialized vanchor note', async () => { + const serialized = + 'webb://v1:vanchor/' + + '1:1/1:1/' + + '0000000000000001:0100000000000000000000000000000000000000000000000000000000000000:a5ae2e56bf539da01d46e9f762faf1fa6cf4547822bd1ec720a10aec2fe6651f:fdda3612a8761648547834e50313935409a1faea9eb27bf2574fc7828c332f26/' + + '?curve=Bn254&width=5&exp=5&hf=Poseidon&backend=Arkworks&token=WEBB&denom=18&amount=1'; + + const note = Note.deserialize(serialized); + + // Trigger the leaf generation to ensure all secrets and there types are correct + note.getLeaf(); + + expect(note.protocol).to.equal('vanchor'); + }); + + it('should fail to deserialize vanchor note with secrets less than 5 (Leaf gen failure)', async () => { + const serialized = + 'webb://v1:vanchor/' + + '1:1/' + + '1:1/' + + '0100000000000000000000000000000000000000000000000000000000000000:c841cfb05415b4fb9872576dc0f7f366cb5cc909e196c53522879a01fa807e0e:4f5cf320dd74031fc6d190e2d17c807828efc03accd6a6c466e09eb4f5aceb13:0002000000000000/' + + '?curve=Bn254&width=5&exp=5&hf=Poseidon&backend=Arkworks&token=WEBB&denom=18&amount=1'; + + const note = Note.deserialize(serialized); + + try { + // Trigger the leaf generation to ensure all secrets and there types are correct + + note.getLeaf(); + } catch (e: any) { + expect(e.code).to.equal(8); + expect(e.message).to.equal('Invalid secret format for protocol vanchor'); + } + + expect(note.protocol).to.equal('vanchor'); + }); + + it('vanchor should fail with secrets 4 secrets', async () => { + const noteInput: NoteGenInput = { + amount: '1', + backend: 'Arkworks', + curve: 'Bn254', + denomination: '18', + exponentiation: '5', + hashFunction: 'Poseidon', + protocol: 'vanchor', + secrets: + '0000000000000001:ae6c3f92db70334231435b03ca139970e2eeff43860171b9f20a0de4b423741e:339e6c9b0a571e612dbcf60e2c20fc58b4e037f00e9384f0f2c872feea91802b', + sourceChain: '1', + sourceIdentifyingData: '1', + targetChain: '1', + targetIdentifyingData: '1', + tokenSymbol: 'WEBB', + version: 'v1', + width: '5', + }; + + try { + Note.generateNote(noteInput); + } catch (e: any) { + expect(e.code).to.equal(8); + expect(e.message).to.equal('VAnchor secrets length should be 4 in length'); + } + }); + + it('should generate vanchor note', async () => { + const noteInput: NoteGenInput = { + amount: '1', + backend: 'Arkworks', + curve: 'Bn254', + denomination: '18', + exponentiation: '5', + hashFunction: 'Poseidon', + protocol: 'vanchor', + sourceChain: '1', + sourceIdentifyingData: '1', + targetChain: '1', + targetIdentifyingData: '1', + tokenSymbol: 'WEBB', + version: 'v1', + width: '5', + }; + const note = Note.generateNote(noteInput); + + const serializedNote = note.serialize(); + const deserializedNote = Note.deserialize(serializedNote); + + expect(deserializedNote.sourceChainId).to.deep.equal('1'); + expect(deserializedNote.sourceIdentifyingData).to.deep.equal('1'); + expect(deserializedNote.targetChainId).to.deep.equal('1'); + expect(deserializedNote.targetIdentifyingData).to.deep.equal('1'); + expect(deserializedNote.backend).to.deep.equal('Arkworks'); + expect(deserializedNote.hashFunction).to.deep.equal('Poseidon'); + expect(deserializedNote.curve).to.deep.equal('Bn254'); + expect(deserializedNote.tokenSymbol).to.deep.equal('WEBB'); + expect(deserializedNote.amount).to.deep.equal('1'); + expect(deserializedNote.denomination).to.deep.equal('18'); + expect(deserializedNote.width).to.deep.equal('5'); + expect(deserializedNote.exponentiation).to.deep.equal('5'); + expect(deserializedNote.version).to.deep.equal('v1'); + expect(deserializedNote.protocol).to.deep.equal('vanchor'); + }); + + it('should update vanchor utxo index successfully', async () => { + const noteInput: NoteGenInput = { + amount: '1', + backend: 'Arkworks', + curve: 'Bn254', + denomination: '18', + exponentiation: '5', + hashFunction: 'Poseidon', + protocol: 'vanchor', + sourceChain: '1', + sourceIdentifyingData: '1', + targetChain: '1', + targetIdentifyingData: '1', + tokenSymbol: 'WEBB', + version: 'v1', + width: '5', + }; + // Note generated + const note = Note.generateNote(noteInput); + const noteWithoutIndex = note.serialize(); + + const miscPartsObj = (note: string): Record => { + return note + .split('?')[1] + .split('&') + .reduce((acc, entry) => { + const [key, value] = entry.split('='); + + return { + ...acc, + [key]: value, + }; + }, {}); + }; + + const miscPartsWithoutIndex: any = miscPartsObj(noteWithoutIndex); + + // No index before mutating + expect(miscPartsWithoutIndex.index).to.deep.equal(undefined); + note.mutateIndex('512'); + + const serializedNote = note.serialize(); + const deserializedNote = Note.deserialize(serializedNote); + const miscPartsWitIndex: any = miscPartsObj(serializedNote); + + expect(miscPartsWitIndex.index).to.deep.equal('512'); + + expect(deserializedNote.sourceChainId).to.deep.equal('1'); + expect(deserializedNote.sourceIdentifyingData).to.deep.equal('1'); + expect(deserializedNote.targetChainId).to.deep.equal('1'); + expect(deserializedNote.targetIdentifyingData).to.deep.equal('1'); + expect(deserializedNote.backend).to.deep.equal('Arkworks'); + expect(deserializedNote.hashFunction).to.deep.equal('Poseidon'); + expect(deserializedNote.curve).to.deep.equal('Bn254'); + expect(deserializedNote.tokenSymbol).to.deep.equal('WEBB'); + expect(deserializedNote.amount).to.deep.equal('1'); + expect(deserializedNote.denomination).to.deep.equal('18'); + expect(deserializedNote.width).to.deep.equal('5'); + expect(deserializedNote.exponentiation).to.deep.equal('5'); + expect(deserializedNote.version).to.deep.equal('v1'); + expect(deserializedNote.protocol).to.deep.equal('vanchor'); + expect(deserializedNote.index).to.deep.equal('512'); + }); +}); diff --git a/packages/utils/src/__test__/typed-chain-id.spec.ts b/packages/utils/src/__test__/typed-chain-id.spec.ts new file mode 100644 index 000000000..933d2c6c6 --- /dev/null +++ b/packages/utils/src/__test__/typed-chain-id.spec.ts @@ -0,0 +1,66 @@ +// Copyright 2022-2023 Webb Technologies Inc. +// SPDX-License-Identifier: Apache-2.0 + +import { expect } from 'chai'; + +import { + byteArrayToNum, + calculateTypedChainId, + ChainType, + numToByteArray, + parseTypedChainId, + TypedChainId, +} from '../protocol/typed-chain-id'; + +describe('test various conversion functions', () => { + it('byte array to num converts correctly', () => { + const arr = [2, 0, 0, 0, 122, 105]; + const result = 2199023286889; + + expect(byteArrayToNum(arr)).to.deep.equal(result); + }); + + it('numToByteArray converts correctly', () => { + const arrResult = [2, 0, 0, 0, 122, 105]; + const number = 2199023286889; + + expect(numToByteArray(number, 4)).to.deep.equal(arrResult); + }); + + it('numToByteArray converts hexstring values correctly', () => { + const evmResult = [1, 0]; + + expect(numToByteArray(ChainType.EVM, 2)).to.deep.equal(evmResult); + const kusamaParachainResult = [3, 17]; + + expect(numToByteArray(ChainType.KusamaParachain, 2)).to.deep.equal(kusamaParachainResult); + }); + + it('numToByteArray maintains minimum size with leading zeroes', () => { + const arrResult = [0, 0, 122, 105]; + const number = 31337; + + expect(numToByteArray(number, 4)).to.deep.equal(arrResult); + }); + + it('calculateTypedChainId converts correctly', () => { + const chainType = ChainType.Substrate; + const chainId = 31337; + const chainIdTypeResult = 2199023286889; + + expect(calculateTypedChainId(chainType, chainId)).to.deep.equal(chainIdTypeResult); + }); + + it('typeAndIdFromChainIdType converts correctly', () => { + const chainIdType = 2199023286889; + const chainTypeResult = ChainType.Substrate; + const chainIdResult = 31337; + + const result: TypedChainId = { + chainId: chainIdResult, + chainType: chainTypeResult, + }; + + expect(parseTypedChainId(chainIdType)).to.deep.equal(result); + }); +}); diff --git a/packages/utils/src/__test__/utxo.spec.ts b/packages/utils/src/__test__/utxo.spec.ts new file mode 100644 index 000000000..7ebedf7ae --- /dev/null +++ b/packages/utils/src/__test__/utxo.spec.ts @@ -0,0 +1,197 @@ +import { expect } from 'chai'; +import { Keypair } from '../protocol/keypair'; +import { Utxo } from '../protocol/utxo'; +import { Utxo as SdkCoreUtxo } from '@webb-tools/sdk-core'; +import { u8aToHex, hexToU8a } from '..'; + +describe('Utxo Class', () => { + it('should construct with params', async function () { + const utxo = Utxo.generateUtxo({ + amount: '1', + backend: 'Circom', + chainId: '1', + curve: 'Bn254', + index: '0', + }); + const nullifier = utxo.nullifier; + const commitment = utxo.commitment; + const blinding = utxo.blinding; + const secretKey = utxo.secret_key; + const amount = utxo.amount; + const index = utxo.index; + const serializedUtxo = utxo.serialize(); + const deserializedUtxo = Utxo.deserialize(serializedUtxo); + const nullifier2 = deserializedUtxo.nullifier; + const commitment2 = deserializedUtxo.commitment; + const blinding2 = deserializedUtxo.blinding; + const secretKey2 = deserializedUtxo.secret_key; + const amount2 = deserializedUtxo.amount; + const index2 = deserializedUtxo.index; + + expect(nullifier2).to.deep.equal(nullifier); + expect(secretKey2).to.deep.equal(secretKey); + expect(blinding2).to.deep.equal(blinding); + expect(amount2).to.deep.equal(amount); + expect(index2).to.deep.equal(index); + expect(u8aToHex(commitment2)).to.deep.equal(u8aToHex(commitment)); + }); + + it('should deserialize and serialize to the same value when all secrets passed', async function () { + const serializedInput = [ + 'Bn254', + 'Circom', + '1000', + '20000000438', + '17415b69c56a3c3897dcb339ce266a0f2a70c9372a6fec1676f81ddaf68e9926', + '2b1297db7d088c2e3fdfa7e626518c5ea6039917e8a75119bc0bb162b30f7a1c', + '258f154ce1eee4af55674c5b7c8b6976864f3f3c5af474c105dad8a372db9850', + '25e8b77121b8fcbf2332970720cd5b51d20a0548ca142eb8ae7814a53225d82c', + '1', + ].join('&'); + const deserialized = Utxo.deserialize(serializedInput); + + const serializedOutput = deserialized.serialize(); + + expect(serializedOutput).to.deep.equal(serializedInput); + }); + + it('should deserialize and serialize a utxo which does not have an index', async function () { + const serializedInput = [ + 'Bn254', + 'Arkworks', + '10000000000000', + '2199023256632', + '17415b69c56a3c3897dcb339ce266a0f2a70c9372a6fec1676f81ddaf68e9926', + '2b1297db7d088c2e3fdfa7e626518c5ea6039917e8a75119bc0bb162b30f7a1c', + '258f154ce1eee4af55674c5b7c8b6976864f3f3c5af474c105dad8a372db9850', + '25e8b77121b8fcbf2332970720cd5b51d20a0548ca142eb8ae7814a53225d82c', + '', + ].join('&'); + + const deserialized = Utxo.deserialize(serializedInput); + const serializedOutput = deserialized.serialize(); + + expect(serializedOutput).to.deep.equal(serializedInput); + }); + + it('should deserialize and serialize a "public utxo"', async function () { + const keypair = Keypair.fromString( + '0x1111111111111111111111111111111111111111111111111111111111111111' + ); + + const utxo = Utxo.generateUtxo({ + amount: '10', + backend: 'Circom', + chainId: '1', + curve: 'Bn254', + keypair, + }); + + const serializedUtxo = utxo.serialize(); + + const deserializedUtxo = Utxo.deserialize(serializedUtxo); + const utxoString = deserializedUtxo.serialize(); + + expect(utxoString).to.deep.equal(serializedUtxo); + }); + + it('Utxo and SdkCoreUtxo should generate compatible outputs public utxo', async function () { + const keypair = Keypair.fromString( + '0x1111111111111111111111111111111111111111111111111111111111111111' + ); + const blinding = hexToU8a( + '0x17415b69c56a3c3897dcb339ce266a0f2a70c9372a6fec1676f81ddaf68e9926', + 256 + ); + + const sdkCoreUtxo = await SdkCoreUtxo.generateUtxo({ + amount: '1000000000', + backend: 'Arkworks', + blinding, + chainId: '2199023256632', + curve: 'Bn254', + index: '0', + keypair: keypair as unknown as any, + }); + const utxo = Utxo.generateUtxo({ + amount: '1000000000', + backend: 'Circom', + blinding, + chainId: '2199023256632', + curve: 'Bn254', + index: '0', + keypair, + }); + + expect(utxo.amount).to.deep.equal(sdkCoreUtxo.amount); + expect(utxo.chainId).to.deep.equal(sdkCoreUtxo.chainId); + expect(utxo.public_key).to.deep.equal(sdkCoreUtxo.public_key); + expect(utxo.blinding).to.deep.equal(sdkCoreUtxo.blinding); + expect(utxo.commitment).to.deep.equal(sdkCoreUtxo.commitment); + }); + + it('Utxo and SdkCoreUtxo should generate compatible outputs private utxo', async function () { + const keypair = new Keypair(); + const blinding = hexToU8a( + '0x17415b69c56a3c3897dcb339ce266a0f2a70c9372a6fec1676f81ddaf68e9926', + 256 + ); + + const sdkCoreUtxo = await SdkCoreUtxo.generateUtxo({ + amount: '1000000000', + backend: 'Arkworks', + blinding, + chainId: '2199023256632', + curve: 'Bn254', + index: '0', + keypair: keypair as unknown as any, + }); + const utxo = Utxo.generateUtxo({ + amount: '1000000000', + backend: 'Circom', + blinding, + chainId: '2199023256632', + curve: 'Bn254', + index: '0', + keypair, + }); + + expect(utxo.amount).to.deep.equal(sdkCoreUtxo.amount); + expect(utxo.chainId).to.deep.equal(sdkCoreUtxo.chainId); + expect(utxo.keypair.toString()).to.deep.equal(sdkCoreUtxo.keypair.toString()); + expect(utxo.public_key).to.deep.equal(sdkCoreUtxo.public_key); + expect(utxo.secret_key).to.deep.equal(sdkCoreUtxo.secret_key); + expect(utxo.blinding).to.deep.equal(sdkCoreUtxo.blinding); + expect(utxo.commitment).to.deep.equal(sdkCoreUtxo.commitment); + expect(utxo.nullifier).to.deep.equal(sdkCoreUtxo.nullifier); + }); + + it('Check valid encryption length', async function () { + const kp = new Keypair(); + + const enc = Keypair.encryptWithKey(kp.getEncryptionKey()!, 'jumbo'); + + try { + Utxo.decrypt(kp, enc); + } catch (ex: any) { + expect(ex.message).to.contain('malformed utxo encryption'); + } + }); + + it('Should set the index of an Utxo', async function () { + const keypair = new Keypair(); + + const utxo = Utxo.generateUtxo({ + amount: '0', + backend: 'Circom', + chainId: '1', + curve: 'Bn254', + index: '0', + keypair, + }); + + utxo.setIndex(2); + + expect(utxo.index).to.eq(2); + }); +}); diff --git a/packages/utils/src/hexToU8a.ts b/packages/utils/src/bytes/hexToU8a.ts similarity index 100% rename from packages/utils/src/hexToU8a.ts rename to packages/utils/src/bytes/hexToU8a.ts diff --git a/packages/utils/src/bytes/index.ts b/packages/utils/src/bytes/index.ts new file mode 100644 index 000000000..42952bffa --- /dev/null +++ b/packages/utils/src/bytes/index.ts @@ -0,0 +1,2 @@ +export * from './hexToU8a'; +export * from './u8aToHex'; diff --git a/packages/utils/src/u8aToHex.ts b/packages/utils/src/bytes/u8aToHex.ts similarity index 100% rename from packages/utils/src/u8aToHex.ts rename to packages/utils/src/bytes/u8aToHex.ts diff --git a/packages/utils/src/fixtures.ts b/packages/utils/src/fixtures.ts index 5017b2044..31fab6b52 100644 --- a/packages/utils/src/fixtures.ts +++ b/packages/utils/src/fixtures.ts @@ -1,9 +1,13 @@ import fs from 'fs'; import path from 'path'; -import { ZkComponents } from '.'; - const snarkjs = require('snarkjs'); +export type ZkComponents = { + wasm: Buffer; + zkey: Uint8Array; + witnessCalculator: any; +}; + export async function fetchComponentsFromFilePaths( wasmPath: string, witnessCalculatorPath: string, diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 8541ff626..23a454b67 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -1,5 +1,5 @@ -export * from './types'; export * from './utils'; -export * from './hexToU8a'; -export * from './u8aToHex'; +export * from './bytes'; export * from './fixtures'; +export * from './protocol'; +export * from './proof'; diff --git a/packages/utils/src/proof/build-variable-witness.ts b/packages/utils/src/proof/build-variable-witness.ts new file mode 100644 index 000000000..c06803f34 --- /dev/null +++ b/packages/utils/src/proof/build-variable-witness.ts @@ -0,0 +1,341 @@ +// Copyright 2022 @webb-tools/ +// SPDX-License-Identifier: Apache-2.0 + +// This file is mostly copied from the fixtures, and is generated by snarkjs. +// @ts-nocheck +/* eslint-disable camelcase */ + +export async function buildVariableWitnessCalculator(code, options) { + options = options || {}; + + let wasmModule; + + try { + wasmModule = await WebAssembly.compile(code); + } catch (err) { + console.log(err); + console.log('\nTry to run circom --c in order to generate c++ code instead\n'); + throw new Error(err); + } + + const instance = await WebAssembly.instantiate(wasmModule, { + runtime: { + exceptionHandler: function (code) { + let errStr; + + if (code === 1) { + errStr = 'Signal not found. '; + } else if (code === 2) { + errStr = 'Too many signals set. '; + } else if (code === 3) { + errStr = 'Signal already set. '; + } else if (code === 4) { + errStr = 'Assert Failed. '; + } else if (code === 5) { + errStr = 'Not enough memory. '; + } else if (code === 6) { + errStr = 'Input signal array access exceeds the size'; + } else { + errStr = 'Unknown error\n'; + } + + // get error message from wasm + errStr += getMessage(); + throw new Error(errStr); + }, + showSharedRWMemory: function () { + printSharedRWMemory(); + }, + }, + }); + + const sanityCheck = options; + // options && + // ( + // options.sanityCheck || + // options.logGetSignal || + // options.logSetSignal || + // options.logStartComponent || + // options.logFinishComponent + // ); + + const wc = new WitnessCalculator(instance, sanityCheck); + + return wc; + + function getMessage() { + let message = ''; + let c = instance.exports.getMessageChar(); + + while (c !== 0) { + message += String.fromCharCode(c); + c = instance.exports.getMessageChar(); + } + + return message; + } + + function printSharedRWMemory() { + const shared_rw_memory_size = instance.exports.getFieldNumLen32(); + const arr = new Uint32Array(shared_rw_memory_size); + + for (let j = 0; j < shared_rw_memory_size; j++) { + arr[shared_rw_memory_size - 1 - j] = instance.exports.readSharedRWMemory(j); + } + } +} + +class WitnessCalculator { + constructor(instance, sanityCheck) { + this.instance = instance; + + this.version = this.instance.exports.getVersion(); + this.n32 = this.instance.exports.getFieldNumLen32(); + + this.instance.exports.getRawPrime(); + const arr = new Array(this.n32); + + for (let i = 0; i < this.n32; i++) { + arr[this.n32 - 1 - i] = this.instance.exports.readSharedRWMemory(i); + } + + this.prime = fromArray32(arr); + + this.witnessSize = this.instance.exports.getWitnessSize(); + + this.sanityCheck = sanityCheck; + } + + circom_version() { + return this.instance.exports.getVersion(); + } + + async _doCalculateWitness(input, sanityCheck) { + // input is assumed to be a map from signals to arrays of bigints + this.instance.exports.init(this.sanityCheck || sanityCheck ? 1 : 0); + const keys = Object.keys(input); + let input_counter = 0; + + keys.forEach((k) => { + const h = fnvHash(k); + const hMSB = parseInt(h.slice(0, 8), 16); + const hLSB = parseInt(h.slice(8, 16), 16); + const fArr = flatArray(input[k]); + const signalSize = this.instance.exports.getInputSignalSize(hMSB, hLSB); + + if (signalSize < 0) { + throw new Error(`Signal ${k} not found\n`); + } + + if (fArr.length < signalSize) { + throw new Error(`Not enough values for input signal ${k}\n`); + } + + if (fArr.length > signalSize) { + throw new Error(`Too many values for input signal ${k}\n`); + } + + for (let i = 0; i < fArr.length; i++) { + const arrFr = toArray32(fArr[i], this.n32); + + for (let j = 0; j < this.n32; j++) { + this.instance.exports.writeSharedRWMemory(j, arrFr[this.n32 - 1 - j]); + } + + try { + this.instance.exports.setInputSignal(hMSB, hLSB, i); + input_counter++; + } catch (err) { + // console.log(`After adding signal ${i} of ${k}`) + throw new Error(err); + } + } + }); + + if (input_counter < this.instance.exports.getInputSize()) { + throw new Error( + `Not all inputs have been set. Only ${input_counter} out of ${this.instance.exports.getInputSize()}` + ); + } + } + + async calculateWitness(input, sanityCheck) { + const w = []; + + await this._doCalculateWitness(input, sanityCheck); + + for (let i = 0; i < this.witnessSize; i++) { + this.instance.exports.getWitness(i); + const arr = new Uint32Array(this.n32); + + for (let j = 0; j < this.n32; j++) { + arr[this.n32 - 1 - j] = this.instance.exports.readSharedRWMemory(j); + } + + w.push(fromArray32(arr)); + } + + return w; + } + + async calculateBinWitness(input, sanityCheck) { + const buff32 = new Uint32Array(this.witnessSize * this.n32); + const buff = new Uint8Array(buff32.buffer); + + await this._doCalculateWitness(input, sanityCheck); + + for (let i = 0; i < this.witnessSize; i++) { + this.instance.exports.getWitness(i); + const pos = i * this.n32; + + for (let j = 0; j < this.n32; j++) { + buff32[pos + j] = this.instance.exports.readSharedRWMemory(j); + } + } + + return buff; + } + + async calculateWTNSBin(input, sanityCheck) { + const buff32 = new Uint32Array(this.witnessSize * this.n32 + this.n32 + 11); + const buff = new Uint8Array(buff32.buffer); + + await this._doCalculateWitness(input, sanityCheck); + + // "wtns" + buff[0] = 'w'.charCodeAt(0); + buff[1] = 't'.charCodeAt(0); + buff[2] = 'n'.charCodeAt(0); + buff[3] = 's'.charCodeAt(0); + + // version 2 + buff32[1] = 2; + + // number of sections: 2 + buff32[2] = 2; + + // id section 1 + buff32[3] = 1; + + const n8 = this.n32 * 4; + // id section 1 length in 64bytes + const idSection1length = 8 + n8; + const idSection1lengthHex = idSection1length.toString(16); + + buff32[4] = parseInt(idSection1lengthHex.slice(0, 8), 16); + buff32[5] = parseInt(idSection1lengthHex.slice(8, 16), 16); + + // this.n32 + buff32[6] = n8; + + // prime number + this.instance.exports.getRawPrime(); + + let pos = 7; + + for (let j = 0; j < this.n32; j++) { + buff32[pos + j] = this.instance.exports.readSharedRWMemory(j); + } + + pos += this.n32; + + // witness size + buff32[pos] = this.witnessSize; + pos++; + + // id section 2 + buff32[pos] = 2; + pos++; + + // section 2 length + const idSection2length = n8 * this.witnessSize; + const idSection2lengthHex = idSection2length.toString(16); + + buff32[pos] = parseInt(idSection2lengthHex.slice(0, 8), 16); + buff32[pos + 1] = parseInt(idSection2lengthHex.slice(8, 16), 16); + + pos += 2; + + for (let i = 0; i < this.witnessSize; i++) { + this.instance.exports.getWitness(i); + + for (let j = 0; j < this.n32; j++) { + buff32[pos + j] = this.instance.exports.readSharedRWMemory(j); + } + + pos += this.n32; + } + + return buff; + } +} + +function toArray32(s, size) { + const res = []; // new Uint32Array(size); //has no unshift + let rem = BigInt(s); + const radix = BigInt(0x100000000); + + while (rem) { + res.unshift(Number(rem % radix)); + rem = rem / radix; + } + + if (size) { + let i = size - res.length; + + while (i > 0) { + res.unshift(0); + i--; + } + } + + return res; +} + +function fromArray32(arr) { + // returns a BigInt + let res = BigInt(0); + const radix = BigInt(0x100000000); + + for (let i = 0; i < arr.length; i++) { + res = res * radix + BigInt(arr[i]); + } + + return res; +} + +function flatArray(a) { + const res = []; + + fillArray(res, a); + + return res; + + function fillArray(res, a) { + if (Array.isArray(a)) { + for (let i = 0; i < a.length; i++) { + fillArray(res, a[i]); + } + } else { + res.push(a); + } + } +} + +function fnvHash(str) { + const uint64_max = BigInt(2) ** BigInt(64); + let hash = BigInt('0xCBF29CE484222325'); + + for (let i = 0; i < str.length; i++) { + hash ^= BigInt(str[i].charCodeAt()); + hash *= BigInt(0x100000001b3); + hash %= uint64_max; + } + + let shash = hash.toString(16); + const n = 16 - shash.length; + + shash = '0'.repeat(n).concat(shash); + + return shash; +} diff --git a/packages/utils/src/proof/index.ts b/packages/utils/src/proof/index.ts new file mode 100644 index 000000000..ae88c1523 --- /dev/null +++ b/packages/utils/src/proof/index.ts @@ -0,0 +1,87 @@ +export * from './variable-anchor.js'; +export * from './build-variable-witness.js'; + +import { ethers } from 'ethers'; +import { p256 } from '../utils'; + +export type Proof = { + pi_a: string[3]; + pi_b: Array; + pi_c: string[3]; + protocol: string; + curve: string; +}; + +export type VAnchorProofInputs = { + roots: string[]; + chainID: string; + inputNullifier: string[]; + outputCommitment: string[]; + publicAmount: string; + extDataHash: string; + + // data for 2 transaction inputs + inAmount: string[]; + inPrivateKey: string[]; + inBlinding: string[]; + inPathIndices: number[][]; + inPathElements: number[][]; + + // data for 2 transaction outputs + outChainID: string; + outAmount: string[]; + outPubkey: string[]; + outBlinding: string[]; +}; + +export function groth16ExportSolidityCallData(proof: any, pub: any) { + let inputs = ''; + + for (let i = 0; i < pub.length; i++) { + if (inputs !== '') { + inputs = inputs + ','; + } + + inputs = inputs + p256(pub[i]); + } + + const S = + `[${p256(proof.pi_a[0])}, ${p256(proof.pi_a[1])}],` + + `[[${p256(proof.pi_b[0][1])}, ${p256(proof.pi_b[0][0])}],[${p256(proof.pi_b[1][1])}, ${p256( + proof.pi_b[1][0] + )}]],` + + `[${p256(proof.pi_c[0])}, ${p256(proof.pi_c[1])}],` + + `[${inputs}]`; + + return S; +} + +export function generateWithdrawProofCallData(proof: any, publicSignals: any) { + const result = groth16ExportSolidityCallData(proof, publicSignals); + const fullProof = JSON.parse('[' + result + ']'); + const pi_a = fullProof[0]; + const pi_b = fullProof[1]; + const pi_c = fullProof[2]; + + const proofEncoded = [ + pi_a[0], + pi_a[1], + pi_b[0][0], + pi_b[0][1], + pi_b[1][0], + pi_b[1][1], + pi_c[0], + pi_c[1], + ] + .map((elt) => elt.substr(2)) + .join(''); + + return proofEncoded; +} + +export const generateFunctionSigHash = (functionSignature: string): string => { + return ethers.utils + .keccak256(ethers.utils.toUtf8Bytes(functionSignature)) + .slice(0, 10) + .padEnd(10, '0'); +}; diff --git a/packages/utils/src/proof/variable-anchor.ts b/packages/utils/src/proof/variable-anchor.ts new file mode 100644 index 000000000..9a741a8ee --- /dev/null +++ b/packages/utils/src/proof/variable-anchor.ts @@ -0,0 +1,84 @@ +/* eslint-disable camelcase */ +/* eslint-disable sort-keys */ +import { BigNumber, BigNumberish, ethers } from 'ethers'; + +import { u8aToHex } from '@polkadot/util'; + +import { toFixedHex } from '../utils'; +import { FIELD_SIZE } from '../protocol'; +import { Utxo } from '../protocol/utxo'; +import { MerkleProof, MerkleTree } from '../protocol/merkle-tree'; + +export function getVAnchorExtDataHash( + encryptedOutput1: string, + encryptedOutput2: string, + extAmount: string, + fee: string, + recipient: string, + relayer: string, + refund: string, + token: string +): BigNumberish { + const abi = new ethers.utils.AbiCoder(); + const encodedData = abi.encode( + [ + 'tuple(address recipient,int256 extAmount,address relayer,uint256 fee,uint256 refund,address token,bytes encryptedOutput1,bytes encryptedOutput2)', + ], + [ + { + recipient: toFixedHex(recipient, 20), + extAmount: toFixedHex(extAmount), + relayer: toFixedHex(relayer, 20), + fee: toFixedHex(fee), + refund: toFixedHex(refund), + token: toFixedHex(token, 20), + encryptedOutput1, + encryptedOutput2, + }, + ] + ); + + const hash = ethers.utils.keccak256(encodedData); + + return BigNumber.from(hash).mod(FIELD_SIZE); +} + +export function generateVariableWitnessInput( + roots: BigNumberish[], + chainId: BigNumberish, + inputs: Utxo[], + outputs: Utxo[], + extAmount: BigNumberish, + fee: BigNumberish, + extDataHash: BigNumberish, + externalMerkleProofs: MerkleProof[] +): any { + const vanchorMerkleProofs = externalMerkleProofs.map((proof) => ({ + pathIndex: MerkleTree.calculateIndexFromPathIndices(proof.pathIndices), + pathElements: proof.pathElements, + })); + + const input = { + roots: roots.map((x) => x.toString()), + chainID: chainId.toString(), + inputNullifier: inputs.map((x) => '0x' + x.nullifier), + outputCommitment: outputs.map((x) => BigNumber.from(u8aToHex(x.commitment)).toString()), + publicAmount: BigNumber.from(extAmount).sub(fee).add(FIELD_SIZE).mod(FIELD_SIZE).toString(), + extDataHash: extDataHash.toString(), + + // data for 2 transaction inputs + inAmount: inputs.map((x) => x.amount.toString()), + inPrivateKey: inputs.map((x) => '0x' + x.secret_key), + inBlinding: inputs.map((x) => BigNumber.from('0x' + x.blinding).toString()), + inPathIndices: vanchorMerkleProofs.map((x) => x.pathIndex), + inPathElements: vanchorMerkleProofs.map((x) => x.pathElements), + + // data for 2 transaction outputs + outChainID: outputs.map((x) => x.chainId), + outAmount: outputs.map((x) => x.amount.toString()), + outPubkey: outputs.map((x) => BigNumber.from(x.getKeypair().getPubKey()).toString()), + outBlinding: outputs.map((x) => BigNumber.from('0x' + x.blinding).toString()), + }; + + return input; +} diff --git a/packages/utils/src/protocol/index.ts b/packages/utils/src/protocol/index.ts new file mode 100644 index 000000000..f6e6a7add --- /dev/null +++ b/packages/utils/src/protocol/index.ts @@ -0,0 +1,15 @@ +import { BigNumber } from 'ethers'; + +export const ZERO_BYTES32 = '0x0000000000000000000000000000000000000000000000000000000000000000'; +export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; + +export const FIELD_SIZE = BigNumber.from( + '21888242871839275222246405745257275088548364400416034343698204186575808495617' +); + +export * from '../utils'; +export * from './merkle-tree'; +export * from './note'; +export * from './utxo'; +export * from './typed-chain-id'; +export * from './keypair'; diff --git a/packages/utils/src/protocol/keypair.ts b/packages/utils/src/protocol/keypair.ts new file mode 100644 index 000000000..e4152277c --- /dev/null +++ b/packages/utils/src/protocol/keypair.ts @@ -0,0 +1,189 @@ +// Copyright 2022 Webb Technologies Inc. +// SPDX-License-Identifier: Apache-2.0 +// This file has been modified by Webb Technologies Inc. + +import { decrypt, encrypt, getEncryptionPublicKey } from '@metamask/eth-sig-util'; +import { poseidon } from 'circomlibjs'; +import { BigNumber, ethers } from 'ethers'; + +import { randomBN, toFixedHex } from '../utils'; +import { FIELD_SIZE } from './'; + +export function packEncryptedMessage(encryptedMessage: any) { + const nonceBuf = Buffer.from(encryptedMessage.nonce, 'base64'); + const ephemPublicKeyBuf = Buffer.from(encryptedMessage.ephemPublicKey, 'base64'); + const ciphertextBuf = Buffer.from(encryptedMessage.ciphertext, 'base64'); + const messageBuff = Buffer.concat([ + Buffer.alloc(24 - nonceBuf.length), + nonceBuf, + Buffer.alloc(32 - ephemPublicKeyBuf.length), + ephemPublicKeyBuf, + ciphertextBuf, + ]); + + return '0x' + messageBuff.toString('hex'); +} + +export function unpackEncryptedMessage(encryptedMessage: any) { + if (encryptedMessage.slice(0, 2) === '0x') { + encryptedMessage = encryptedMessage.slice(2); + } + + const messageBuff = Buffer.from(encryptedMessage, 'hex'); + const nonceBuf = messageBuff.subarray(0, 24); + const ephemPublicKeyBuf = messageBuff.subarray(24, 56); + const ciphertextBuf = messageBuff.subarray(56); + + return { + ciphertext: ciphertextBuf.toString('base64'), + ephemPublicKey: ephemPublicKeyBuf.toString('base64'), + nonce: nonceBuf.toString('base64'), + version: 'x25519-xsalsa20-poly1305', + }; +} + +/** + * A Keypair is an object that can group relevant keys for a user in the webb system. + * The keys managed by a keypair are as follows: + * - pubkey: Required + * - used in commitments of circuits, to indicate ownership of a UTXO. + * The value can be derived from `pubkey = poseidon(privkey)` + * - encryptionKey: Optional + * - used to encrypting data for private communication. It is a pubkey of the privkey + * in a different cryptography scheme. + * - privkey: Optional + * - used for proving knowledge of a value and thus ability to spend UTXOs (creating nullifier). + * - used for decrypting data that has been encrypted with the encryptionKey. + */ +export class Keypair { + // Stored as a hex-encoded 0x-prefixed 32 byte string + privkey: string | undefined; + private pubkey: ethers.BigNumber = BigNumber.from(0); + // Stored as a base64 encryption key + private encryptionKey: string | undefined; + + /** + * Initialize a new keypair from a passed hex string. Generates a random private key if not defined. + * + * @param privkey - hex string of a field element for the + * @returns - A 'Keypair' object with pubkey and encryptionKey values derived from the private key. + */ + constructor(privkey = randomBN(32).toHexString()) { + this.privkey = toFixedHex(BigNumber.from(privkey).mod(FIELD_SIZE)); + this.pubkey = BigNumber.from(poseidon([this.privkey])); + this.encryptionKey = getEncryptionPublicKey(this.privkey.slice(2)); + } + + /** + * @returns a string of public parts of this keypair object: pubkey and encryption key. + */ + toString() { + let retVal = toFixedHex(this.pubkey); + + if (this.encryptionKey) { + retVal = retVal + Buffer.from(this.encryptionKey, 'base64').toString('hex'); + } + + return retVal; + } + + /** + * Initialize new keypair from string data. + * + * @param str - A string which contains public keydata. + * (0, 66), the slice for the pubKey value, 0x-prefixed, which is required. + * (66, 130), the slice for the encryptionKey value, which is optional to enable + * encrypt and decrypt functionality. It should be hex encoded. + * @returns The keypair object configured with appropriate public values. + * @throws If the string object is not 66 chars or 130 chars. + */ + static fromString(str: string): Keypair { + if (str.length === 66) { + return Object.assign(new Keypair(), { + encryptionKey: undefined, + privkey: undefined, + pubkey: BigNumber.from(str), + }); + } else if (str.length === 130) { + return Object.assign(new Keypair(), { + encryptionKey: Buffer.from(str.slice(66, 130), 'hex').toString('base64'), + privkey: undefined, + pubkey: BigNumber.from(str.slice(0, 66)), + }); + } else { + throw new Error('Invalid string passed'); + } + } + + // This static method supports encrypting a base64-encoded data, + // with the provided hex-encoded encryption key + static encryptWithKey(encryptionKey: string, data: string) { + const base64Key = Buffer.from(encryptionKey.slice(2), 'hex').toString('base64'); + + return packEncryptedMessage( + encrypt({ + data, + publicKey: base64Key, + version: 'x25519-xsalsa20-poly1305', + }) + ); + } + + /** + * Encrypt data using keypair encryption key + * + * @param bytes - A buffer to encrypt + * @returns a hex string encoding of encrypted data with this encryption key + */ + encrypt(bytes: Buffer) { + if (!this.encryptionKey) { + throw new Error('Cannot encrypt without a configured encryption key'); + } + + return packEncryptedMessage( + encrypt({ + data: bytes.toString('base64'), + publicKey: this.encryptionKey, + version: 'x25519-xsalsa20-poly1305', + }) + ); + } + + /** + * Decrypt data using keypair private key + * + * @param data - a hex string with data + * @returns A Buffer of the decrypted data + */ + decrypt(data: string) { + if (!this.privkey) { + throw new Error('Cannot decrypt without a configured private key'); + } + + return Buffer.from( + decrypt({ + encryptedData: unpackEncryptedMessage(data), + privateKey: this.privkey.slice(2), + }), + 'base64' + ); + } + + /** + * @returns a 0x-prefixed, 32 fixed byte hex-string representation of the public key + */ + getPubKey() { + return toFixedHex(this.pubkey.toHexString()); + } + + /** + * @returns a 0x-prefixed, 32 fixed byte hex-string representation of the encryption key + */ + getEncryptionKey() { + if (!this.encryptionKey) { + return undefined; + } + + return '0x' + Buffer.from(this.encryptionKey, 'base64').toString('hex'); + } +} diff --git a/packages/utils/src/protocol/merkle-tree.ts b/packages/utils/src/protocol/merkle-tree.ts new file mode 100644 index 000000000..d81b9456c --- /dev/null +++ b/packages/utils/src/protocol/merkle-tree.ts @@ -0,0 +1,327 @@ +// Copyright 2022-2023 Webb Technologies Inc. +// SPDX-License-Identifier: Apache-2.0 +// This file has been modified by Webb Technologies Inc. + +import { poseidon } from 'circomlibjs'; +import { BigNumber, BigNumberish } from 'ethers'; + +import { toFixedHex } from '../utils'; + +const DEFAULT_ZERO: BigNumberish = + '21663839004416932945382355908790599225266501822907911457504978515578255421292'; + +function poseidonHash(left: BigNumberish, right: BigNumberish) { + return BigNumber.from(poseidon([BigNumber.from(left), BigNumber.from(right)])); +} + +export type MerkleProof = { + element: BigNumber; + merkleRoot: BigNumber; + pathElements: BigNumber[]; + pathIndices: number[]; +}; + +/** + * Merkle tree + */ +export class MerkleTree { + levels: number; + capacity: number; + _hash: (left: BigNumberish, right: BigNumberish) => BigNumber; + zeroElement: BigNumber; + _zeros: BigNumber[]; + _layers: BigNumber[][]; + + /** + * Constructor + * @param levels - Number of levels in the tree + * @param elements - BigNumberish[] of initial elements + * @param options - Object with the following properties: + * hashFunction - Function used to hash 2 leaves + * zeroElement - Value for non-existent leaves + */ + constructor( + levels: number | string, + elements: BigNumberish[] = [], + { hashFunction = poseidonHash, zeroElement = DEFAULT_ZERO } = {} + ) { + levels = Number(levels); + this.levels = levels; + this.capacity = 2 ** levels; + + if (elements.length > this.capacity) { + throw new Error('Tree is full'); + } + + this._hash = hashFunction; + this.zeroElement = BigNumber.from(zeroElement); + this._zeros = []; + this._zeros[0] = BigNumber.from(zeroElement); + + for (let i = 1; i <= levels; i++) { + this._zeros[i] = this._hash(this._zeros[i - 1], this._zeros[i - 1]); + } + + this._layers = []; + this._layers[0] = elements.slice().map((e) => BigNumber.from(e)); + this._rebuild(); + } + + _rebuild() { + for (let level = 1; level <= this.levels; level++) { + this._layers[level] = []; + + for (let i = 0; i < Math.ceil(this._layers[level - 1].length / 2); i++) { + this._layers[level][i] = this._hash( + this._layers[level - 1][i * 2], + i * 2 + 1 < this._layers[level - 1].length + ? this._layers[level - 1][i * 2 + 1] + : this._zeros[level - 1] + ); + } + } + } + + /** + * Get tree root + * @returns + */ + root(): BigNumber { + return this._layers[this.levels].length > 0 + ? this._layers[this.levels][0] + : this._zeros[this.levels]; + } + + /** + * Insert new element into the tree + * @param element - Element to insert + */ + insert(element: BigNumberish) { + if (this._layers[0].length >= this.capacity) { + throw new Error('Tree is full'); + } + + this.update(this._layers[0].length, BigNumber.from(element)); + } + + bulkRemove(elements: BigNumberish[]) { + for (const elem of elements) { + this.remove(elem); + } + } + + remove(element: BigNumberish) { + const index = this.indexOf(element); + + if (index === -1) { + throw new Error('Element is not in the merkle tree'); + } + + this.removeByIndex(index); + } + + removeByIndex(index: number) { + this.update(index, this.zeroElement); + } + + /** + * Insert multiple elements into the tree. + * @param elements - Elements to insert + */ + bulkInsert(elements: BigNumberish[]) { + if (this._layers[0].length + elements.length > this.capacity) { + throw new Error('Tree is full'); + } + + // First we insert all elements except the last one + // updating only full subtree hashes (all layers where inserted element has odd index) + // the last element will update the full path to the root making the tree consistent again + for (let i = 0; i < elements.length - 1; i++) { + this._layers[0].push(BigNumber.from(elements[i])); + let level = 0; + let index = this._layers[0].length - 1; + + while (index % 2 === 1) { + level++; + index >>= 1; + this._layers[level][index] = this._hash( + this._layers[level - 1][index * 2], + this._layers[level - 1][index * 2 + 1] + ); + } + } + + this.insert(elements[elements.length - 1]); + } + + /** + * Change an element in the tree + * @param index - Index of element to change + * @param element - Updated element value + */ + update(index: number, element: BigNumberish) { + if ( + isNaN(Number(index)) || + index < 0 || + index > this._layers[0].length || + index >= this.capacity + ) { + throw new Error('Insert index out of bounds: ' + index); + } + + this._layers[0][index] = BigNumber.from(element); + + for (let level = 1; level <= this.levels; level++) { + index >>= 1; + this._layers[level][index] = this._hash( + this._layers[level - 1][index * 2], + index * 2 + 1 < this._layers[level - 1].length + ? this._layers[level - 1][index * 2 + 1] + : this._zeros[level - 1] + ); + } + } + + /** + * Get merkle path to a leaf + * @param index - Leaf index to generate path for + * @returns pathElements: Object[], pathIndex: number[] - An object containing adjacent elements and left-right index + */ + path(index: number): MerkleProof { + if (isNaN(Number(index)) || index < 0 || index >= this._layers[0].length) { + throw new Error('Index out of bounds: ' + index); + } + + const pathElements = []; + const pathIndices = []; + + for (let level = 0; level < this.levels; level++) { + pathIndices[level] = index % 2; + pathElements[level] = + (index ^ 1) < this._layers[level].length + ? this._layers[level][index ^ 1] + : this._zeros[level]; + index >>= 1; + } + + return { + element: this._layers[0][index], + merkleRoot: this.root(), + pathElements, + pathIndices, + }; + } + + /** + * Find an element in the tree + * @param element - An element to find + * @returns number - Index if element is found, otherwise -1 + */ + indexOf(element: BigNumberish): number { + return this._layers[0].findIndex((el) => el.eq(BigNumber.from(element))); + } + + /** + * Returns a copy of non-zero tree elements + * @returns Object[] + */ + elements() { + return this._layers[0].slice(); + } + + /** + * Returns a copy of n-th zero elements array + * @returns Object[] + */ + zeros() { + return this._zeros.slice(); + } + + /** + * Serialize entire tree state including intermediate layers into a plain object + * Deserializing it back will not require to recompute any hashes + * Elements are not converted to a plain type, this is responsibility of the caller + */ + serialize() { + return { + _layers: this._layers, + _zeros: this._zeros, + levels: this.levels, + }; + } + + number_of_elements() { + return this._layers[0].length; + } + + getIndexByElement(element: BigNumberish): number { + return this.indexOf(element); + } + + /** + * Deserialize data into a MerkleTree instance + * Make sure to provide the same hashFunction as was used in the source tree, + * otherwise the tree state will be invalid + */ + static deserialize(data: any, hashFunction: any) { + const instance = Object.assign(Object.create(this.prototype), data); + + instance._hash = hashFunction || poseidon; + instance.capacity = 2 ** instance.levels; + instance.zeroElement = instance._zeros[0]; + + return instance; + } + + /** + * Create a merkle tree with the target root by inserting the given leaves + * one-by-one. + * If the root matches after an insertion, return the tree. + * Else, return undefined. + * + * @param leaves - An array of ordered leaves to be inserted in a merkle tree + * @param targetRoot - The root that the caller is trying to build a tree against + * @returns MerkleTree | undefined + */ + static createTreeWithRoot( + levels: number, + leaves: string[], + targetRoot: string + ): MerkleTree | undefined { + if (leaves.length > Math.pow(2, levels)) { + return undefined; + } + + const tree = new MerkleTree(levels, []); + + for (let i = 0; i < leaves.length; i++) { + tree.insert(leaves[i]); + const nextRoot = tree.root(); + + if (toFixedHex(nextRoot) === targetRoot) { + return tree; + } + } + + return undefined; + } + + /** + * This function calculates the desired index given the pathIndices + * + * @param pathIndices - an array of (0, 1) values representing (left, right) selection + * of nodes for each level in the merkle tree. The leaves level of the tree is at index 0 + * and the root of the tree is at index 'levels' + */ + static calculateIndexFromPathIndices(pathIndices: number[]) { + return pathIndices.reduce((value, isRight, level) => { + let addedValue = value; + + if (isRight) { + addedValue = value + 2 ** level; + } + + return addedValue; + }); + } +} diff --git a/packages/utils/src/protocol/note.ts b/packages/utils/src/protocol/note.ts new file mode 100644 index 000000000..3bfdbf29c --- /dev/null +++ b/packages/utils/src/protocol/note.ts @@ -0,0 +1,507 @@ +// Copyright 2022-2023 Webb Technologies Inc. +// SPDX-License-Identifier: Apache-2.0 + +//! An example of a serialized note is: +//! webb://v1:vanchor/ +//! 1099511707777:1099511627781/ +//! 0x38e7aa90c77f86747fab355eecaa0c2e4c3a463d:0x38e7aa90c77f86747fab355eecaa0c2e4c3a463d/ +//! 0000010000000005:000000000000000000000000000000000000000000000000002386f26fc10000:01375c5486fdf98350ba7a2ad013f7ea72f1dc6f63a674a9bc295938fa76a44c:00d3f9f63755b6038415db5ad7c0eff1be61a09a9f108f25c59934a7d4d6821c/ +//! ?curve=Bn254&width=5&exp=5&hf=Poseidon&backend=Circom&token=webbAlpha&denom=18&amount=10000000000000000&index=0 + +import { BigNumberish, BigNumber } from 'ethers'; +import { Utxo } from './utxo'; +import { randomBN, randomFieldElement, toFixedHex } from '../utils'; +import { Keypair } from './keypair'; +import { calculateTypedChainIdBytes, parseTypedChainId } from './typed-chain-id'; +export type Scheme = 'webb'; + +export type NoteProtocol = 'vanchor'; + +// export type Leaves = Array; + +// export type Indices = Array; + +export type HashFunction = 'Poseidon'; + +export type Curve = 'Bn254'; + +export type Version = 'v1'; + +export type Backend = 'Circom' | 'Arkworks'; + +/** + * The note input used to generate a `Note` instance. + * + * @param protocol - The shielded pool protocol to use. + * @param version - The version of the note to use. + * @param sourceChain - The source chain id. + * @param sourceIdentifyingData - source identifying data. + * @param targetChain - The target chain id. + * @param targetIdentifyingData - target identifying data. + * @param backend - The backend to use. Different values include 'Arkworks' and 'Circom' + * @param hashFunction - The hash function to use. Different values include 'Poseidon' and 'Pederson' + * @param curve - The curve to use. Different values include 'Bn254' and 'Bls381' + * @param tokenSymbol - The token symbol to use. + * @param amount - The amount to use. + * @param denomination - The denomination to use. Commonly used denominations include '18' for ETH and '12' for DOT + * @param width - The width to use. Related to the amount of secret parameters hashed together. + * @param secrets - Optional secrets to use. When passed, secret generation is skipped for the resulting note instance. + * @param exponentiation - The exponentiation to use. This is the exponentiation of the SBOX hash function component (for Poseidon) + * @param index - UTXO index. Useful identifying information for deposits in merkle trees. + * @param privateKey - Utxo private key used for generation VAnchor notes + * @param blinding - Utxo blinding value used for generation VAnchor notes + */ +export type NoteGenInput = { + protocol: NoteProtocol; + sourceChain: string; + sourceIdentifyingData: string; + targetChain: string; + targetIdentifyingData: string; + backend: Backend; + hashFunction: HashFunction; + curve: Curve; + tokenSymbol: string; + amount: string; + denomination: string; + width: string; + exponentiation: string; + version?: string; + secrets?: string; + index?: string; + privateKey?: Uint8Array; + blinding?: Uint8Array; +}; + +enum ErrorCode { + Unknown = 'Unknown error', + InvalidHexLength = 'Invalid hex length', + HexParsingFailed = 'Failed to parse hex', + InvalidNoteLength = 'Invalid note length', + InvalidNoteProtocol = 'Invalid note protocol', + InvalidNoteVersion = 'Invalid note version', + InvalidNoteId = 'Invalid note id', + InvalidNoteBlockNumber = 'Invalid block number', + InvalidNoteSecrets = 'Invalid note secrets', + MerkleTreeNotFound = 'Merkle tree not found', + SerializationFailed = 'Failed to serialize', + DeserializationFailed = 'Failed to deserialize', + InvalidArrayLength = 'Invalid array length', + InvalidCurve = 'Invalid curve', + InvalidHasFunction = 'Invalid hash function', + InvalidBackend = 'Invalid backend', + InvalidDenomination = 'Invalid denomination', + SecretGenFailed = 'Failed to generate secrets', + InvalidSourceChain = 'Invalid source chain id', + InvalidTargetChain = 'Invalid target chain id', + InvalidTokenSymbol = 'Invalid token symbol', + InvalidExponentiation = 'Invalid exponentiation', + InvalidWidth = 'Invalid width', + InvalidAmount = 'Invalid amount', + InvalidProofParameters = 'Invalid proof parameters', + InvalidProvingKey = 'Invalid proving key', + InvalidRecipient = 'Invalid recipient address', + InvalidRelayer = 'Invalid relayer address', + InvalidLeafIndex = 'Invalid leaf index', + InvalidFee = 'Invalid fee', + InvalidRefund = 'Invalid refund', + InvalidLeaves = 'Invalid leaves', + FailedToGenerateTheLeaf = 'Failed to generate the leaf', + ProofBuilderNoteNotSet = "Proof building failed note isn't set", + CommitmentNotSet = "Proof building failed refresh commitment isn't set", + RootsNotSet = "Proof building failed roots array isn't set", + InvalidNoteMiscData = 'Invalid note misc data', + InvalidSourceIdentifyingData = 'Invalid source identifying data', + InvalidTargetIdentifyingData = 'Invalid target identifying data', + UnsupportedParameterCombination = 'Unsupported Paramater combination to generate proof', + InvalidProof = 'Proof verification failed', + InvalidUTXOIndex = 'Invalid UTXO Index value', + UnsupportedBackend = 'Unsupported backend', + PublicAmountNotSet = 'VAnchor proof input requires public amount field', + VAnchorProofChainId = 'VAnchor proof input requires chain id', + VAnchorNotesNotSet = 'VAnchor proof input requires list of notes', + VAnchorProofIndices = 'VAnchor proof input require list of indices', + VAnchorProofLeavesMap = 'VAnchor proof input require leaves map', + ProofInputFieldInstantiationError = 'The proof input field installation failed', + ProofInputFieldInstantiationProtocolInvalid = 'The proof input field installation failed wrong protocol or field', + InvalidNullifer = 'Invalid nullifer value', + InvalidRoots = 'Invalid roots value', + InvalidChainId = 'Invalid chain id', + InvalidIndices = 'Invalid indices value', + InvalidPublicAmount = 'Invalid public amount', + InvalidOutputUtxoConfig = 'Invalid output UTXO config', + InvalidExtDataHash = 'Invalid external data hash', + InvalidInputUtxoConfig = 'Invalid input UTXO config', +} + +class SerializationError extends Error { + code: ErrorCode; + data: any; + message: string; + + constructor(code: ErrorCode, data: any, message: string) { + super(message); + this.code = code; + this.data = data; + this.message = message; + } +} + +function handleError(e: any) { + if (e instanceof SerializationError) { + return Promise.reject({ + code: e.code, + data: e.data, + message: e.message, + }); + } else { + return Promise.reject({ + code: 'UnknownError', + data: e, + message: 'Unknown error', + }); + } +} + +/** + * Note class using the WebAssembly note backend. + * + * The goal of this class is to provide a Note interface + * that works both in Node.js and in the browser. + */ +export class Note { + static CURRENT_VERSION: Version = 'v1'; + + scheme: Scheme; + protocol: NoteProtocol; + version: Version; + sourceChainId: string; + targetChainId: string; + sourceIdentifyingData: string; + targetIdentifyingData: string; + secrets: Array; + curve: Curve; + exponentiation: string; + width: string; + tokenSymbol: string; + amount: BigNumberish; + denomination: string; + backend: Backend; + hashFunction: HashFunction; + index: string; + utxo: Utxo; + + // Default constructor + constructor(noteInput: NoteGenInput) { + this.scheme = 'webb'; + this.protocol = noteInput.protocol; + this.version = Note.CURRENT_VERSION; + this.sourceChainId = noteInput.sourceChain; + this.targetChainId = noteInput.targetChain; + this.sourceIdentifyingData = noteInput.sourceIdentifyingData; + this.targetIdentifyingData = noteInput.targetIdentifyingData; + this.secrets = noteInput.secrets ? noteInput.secrets.split(':') : []; + this.curve = noteInput.curve; + this.exponentiation = noteInput.exponentiation; + this.width = noteInput.width; + this.tokenSymbol = noteInput.tokenSymbol; + this.amount = noteInput.amount; + this.denomination = noteInput.denomination; + this.backend = noteInput.backend; + this.hashFunction = noteInput.hashFunction; + this.index = noteInput.index; + + let blinding = noteInput.blinding ? noteInput.blinding : randomFieldElement(32); + let privateKey = noteInput.privateKey ? noteInput.privateKey : randomFieldElement(32); + let keypair = new Keypair(`0x${Buffer.from(privateKey).toString('hex')}`); + + if (this.secrets.length === 0) { + this.secrets = [ + calculateTypedChainIdBytes(Number(this.targetChainId)), + toFixedHex(this.amount, 32).slice(2), + Buffer.from(privateKey).toString().slice(2), + Buffer.from(blinding).toString().slice(2), + ]; + } + + this.utxo = Utxo.generateUtxo({ + curve: this.curve, + backend: this.backend, + amount: this.amount, + chainId: this.targetChainId, + blinding: blinding, + index: this.index, + keypair: keypair, + originChainId: this.sourceChainId, + }); + return this; + } + + /** + * Deserializes a note from a string. + * + * @param value - A serialized note. + * @returns A note class instance. + */ + public static deserialize(value: string): Note { + try { + let schemeAndParts = value.split('://'); + let scheme = schemeAndParts[0]; + // Check scheme + if (scheme !== 'webb') { + throw new SerializationError(ErrorCode.Unknown, scheme, `Unknown scheme ${scheme}`); + } + // Check parts length + let parts = schemeAndParts[1].split('/'); + if (parts.length < 5) { + throw new SerializationError( + ErrorCode.InvalidNoteLength, + parts, + `Expected at least 5 parts, got ${parts.length}` + ); + } + // Check note parts + let versioning = parts[0]; + let chain_ids = parts[1]; + let chainIdentifyingData = parts[2]; + let secrets = parts[3]; + let misc = parts[4].replace('?', ''); + // Parse version/protocol part + let versioningParts = versioning.split(':'); + if (versioningParts.length !== 2) { + throw new SerializationError( + ErrorCode.InvalidNoteLength, + versioning, + `Expected at least 2 versioning parts, got ${versioningParts.length}` + ); + } + let version = versioningParts[0]; + let protocol = versioningParts[1]; + if (version !== Note.CURRENT_VERSION) { + throw new SerializationError( + ErrorCode.InvalidNoteVersion, + version, + `Expected version ${Note.CURRENT_VERSION}, got ${version}` + ); + } + if (protocol !== 'vanchor') { + throw new SerializationError( + ErrorCode.InvalidNoteProtocol, + protocol, + `Expected protocol vanchor, got ${protocol}` + ); + } + // Parse source / target chain ids + let chainIdsParts = chain_ids.split(':'); + if (chainIdsParts.length !== 2) { + throw new SerializationError( + ErrorCode.InvalidNoteLength, + chain_ids, + `Expected at least 2 chain ids parts, got ${chainIdsParts.length}` + ); + } + let sourceChainId = chainIdsParts[0]; + let targetChainId = chainIdsParts[1]; + // Parse source / target chain identifying data + let chainIdentifyingDataParts = chainIdentifyingData.split(':'); + if (chainIdentifyingDataParts.length !== 2) { + throw new SerializationError( + ErrorCode.InvalidNoteLength, + chainIdentifyingData, + `Expected at least 2 chain identifying data parts, got ${chainIdentifyingDataParts.length}` + ); + } + let sourceIdentifyingData = chainIdentifyingDataParts[0]; + let targetIdentifyingData = chainIdentifyingDataParts[1]; + // Misc data parsing + let miscParts = misc.split('&'); + let curve, + width, + exponentiation, + hashFunction, + backend, + tokenSymbol, + denomination, + amount, + index; + + for (let i = 0; i < miscParts.length; i++) { + let miscPart = miscParts[i]; + let miscPartItems = miscPart.split('='); + if (miscPartItems.length !== 2) { + throw new SerializationError( + ErrorCode.InvalidNoteMiscData, + miscPart, + `Expected at least 2 misc data parts, got ${miscPartItems.length}` + ); + } + + let miscPartKey = miscPartItems[0]; + let miscPartValue = miscPartItems[1]; + switch (miscPartKey) { + case 'curve': + curve = miscPartValue; + break; + case 'width': + width = miscPartValue; + break; + case 'exp': + exponentiation = miscPartValue; + break; + case 'hf': + hashFunction = miscPartValue; + break; + case 'backend': + backend = miscPartValue; + break; + case 'token': + tokenSymbol = miscPartValue; + break; + case 'denom': + denomination = miscPartValue; + break; + case 'amount': + amount = miscPartValue; + break; + case 'index': + index = miscPartValue; + break; + default: + throw new SerializationError( + ErrorCode.InvalidNoteMiscData, + miscPartKey, + `Unknown misc data key ${miscPartKey}` + ); + } + } + + let secretsParts = secrets.split(':'); + if (secretsParts.length !== 4) { + throw new SerializationError( + ErrorCode.InvalidNoteSecrets, + secrets, + `Expected 4 secrets parts, got ${secretsParts.length}` + ); + } + let privateKey = secretsParts[2]; + let blinding = secretsParts[3]; + + let noteGenInput: NoteGenInput = { + protocol: protocol, + version: version, + sourceChain: sourceChainId, + sourceIdentifyingData: sourceIdentifyingData, + targetChain: targetChainId, + targetIdentifyingData: targetIdentifyingData, + backend: backend || 'Circom', + hashFunction: hashFunction || 'Poseidon', + curve: curve || 'Bn254', + tokenSymbol: tokenSymbol || '', + amount: amount || '', + denomination: denomination || '', + width: width || '', + exponentiation: exponentiation || '', + secrets: secrets, + index: index || '', + privateKey: Buffer.from(privateKey, 'hex'), + blinding: Buffer.from(blinding, 'hex'), + }; + + return new Note(noteGenInput); + } catch (e: any) { + handleError(e); + } + } + + /** + * Serializes the note to a string. + * + * @returns The serialized note. + */ + public serialize(): string { + let schemeAndVersion = `${this.scheme}://${this.version}:${this.protocol}/`; + let chainIds = `${this.sourceChainId}:${this.targetChainId}/`; + let chainIdentifyingData = `${this.sourceIdentifyingData}:${this.targetIdentifyingData}/`; + let secrets = `${this.secrets.join(':')}/`; + let miscParts = []; + if (this.curve) { + miscParts.push(`curve=${this.curve}`); + } + if (this.width) { + miscParts.push(`width=${this.width}`); + } + if (this.exponentiation) { + miscParts.push(`exp=${this.exponentiation}`); + } + if (this.hashFunction) { + miscParts.push(`hf=${this.hashFunction}`); + } + if (this.backend) { + miscParts.push(`backend=${this.backend}`); + } + if (this.tokenSymbol) { + miscParts.push(`token=${this.tokenSymbol}`); + } + if (this.denomination) { + miscParts.push(`denom=${this.denomination}`); + } + if (this.amount) { + miscParts.push(`amount=${this.amount}`); + } + if (this.index) { + miscParts.push(`index=${this.index}`); + } + let misc = `?${miscParts.join('&')}`; + return `${schemeAndVersion}${chainIds}${chainIdentifyingData}${secrets}${misc}`; + } + + /** + * Calls the webassembly JsNote's mutate index to change the index. + * + * @returns void + */ + mutateIndex(index: string) { + this.index = index; + } + + /** + * Gets the leaf commitment of the note depending + * on the protocol. + * + * @returns Returns the leaf commitment of the note. + */ + getLeaf(): Uint8Array { + return this.utxo.commitment; + } + + /** + * Generates a note using the relevant input data. Supports + * the protocols defined in the WebAssembly note backend. + * + * ```typescript + * // Generate an anchor note + * const input: NoteGenInput = { + * protocol: 'vanchor', + * version: 'v1', + * targetChain: '1', + * targetIdentifyingData: '1', + * sourceChain: '1', + * sourceIdentifyingData: '1', + * backend: 'Circom', + * hashFunction: 'Poseidon', + * curve: 'Bn254', + * tokenSymbol: 'WEBB', + * amount: '1', + * denomination: '18', + * width: '4', + * exponentiation: '5', + * } + * + * const note = await Note.generateNote(input); + * ``` + * @param noteGenInput - The input data for generating a note. + * @returns + */ + public static generateNote(noteGenInput: NoteGenInput): Note { + return new Note(noteGenInput); + } +} diff --git a/packages/utils/src/protocol/typed-chain-id.ts b/packages/utils/src/protocol/typed-chain-id.ts new file mode 100644 index 000000000..b60c1e96f --- /dev/null +++ b/packages/utils/src/protocol/typed-chain-id.ts @@ -0,0 +1,99 @@ +// Copyright 2022-2023 Webb Technologies Inc. +// SPDX-License-Identifier: Apache-2.0 + +// Each ChainType has its own namespace of ChainIDs. +export enum ChainType { + None = 0x0000, + EVM = 0x0100, + Substrate = 0x0200, + SubstrateDevelopment = 0x0250, + PolkadotRelayChain = 0x0301, + KusamaRelayChain = 0x0302, + PolkadotParachain = 0x0310, + KusamaParachain = 0x0311, + Cosmos = 0x0400, + Solana = 0x0500, +} + +export function castToChainType(v: number): ChainType { + switch (v) { + case 0x0100: + return ChainType.EVM; + case 0x0200: + return ChainType.Substrate; + case 0x0301: + return ChainType.PolkadotRelayChain; + case 0x0302: + return ChainType.KusamaRelayChain; + case 0x0310: + return ChainType.PolkadotParachain; + case 0x0311: + return ChainType.KusamaParachain; + case 0x0400: + return ChainType.Cosmos; + case 0x0500: + return ChainType.Solana; + default: + return ChainType.None; + } +} + +export type TypedChainId = { + chainType: ChainType; + chainId: number; +}; + +/** + * @param num - the number to be converted + * @param min - the minimum bytes the array should hold (in the case of requiring empty bytes to match rust values) + * @returns + */ +export const numToByteArray = (num: number, min: number): number[] => { + let arr = []; + + while (num > 0) { + arr.push(num % 256); + num = Math.floor(num / 256); + } + + arr.reverse(); + + // maintain minimum number of bytes + while (arr.length < min) { + arr = [0, ...arr]; + } + + return arr; +}; + +export const byteArrayToNum = (arr: number[]): number => { + let n = 0; + + for (const i of arr) { + n = n * 256 + i; + } + + return n; +}; + +export const calculateTypedChainId = (chainType: ChainType, chainId: number): number => { + const chainTypeArray = numToByteArray(chainType, 2); + const chainIdArray = numToByteArray(chainId, 4); + const fullArray = [...chainTypeArray, ...chainIdArray]; + + return byteArrayToNum(fullArray); +}; + +export const calculateTypedChainIdBytes = (typedChainId: number): string => { + // Return big endian 8 bytes (64 bits) representation of typedChainId as a string + const bytes = new Uint8Array(numToByteArray(typedChainId, 8)); + return Buffer.from(bytes).toString('hex'); +}; + +export const parseTypedChainId = (chainIdType: number): TypedChainId => { + const byteArray = numToByteArray(chainIdType, 4); + const chainType = byteArrayToNum(byteArray.slice(0, 2)); + const chainId = byteArrayToNum(byteArray.slice(2)); + + return { chainId, chainType }; +}; diff --git a/packages/utils/src/protocol/utxo.ts b/packages/utils/src/protocol/utxo.ts new file mode 100644 index 000000000..ee17d1d9e --- /dev/null +++ b/packages/utils/src/protocol/utxo.ts @@ -0,0 +1,297 @@ +import { poseidon } from 'circomlibjs'; +import { BigNumber } from 'ethers'; +import { randomBN, toBuffer, toFixedHex } from '../utils'; +import { Keypair } from './keypair'; +import { Curve, Backend } from './note'; +import { hexToU8a, u8aToHex } from '../bytes'; + +export type UtxoGenInput = { + curve: Curve; + backend: Backend; + amount: string; + chainId: string; + blinding?: Uint8Array; + index?: string; + keypair?: Keypair; + originChainId?: string; +}; + +export class Utxo { + _keypair: Keypair = new Keypair(); + _curve: Curve = 'Bn254'; + _backend: Backend = 'Circom'; + _amount = ''; + _chainId = ''; + _index?: number; + _pubkey = ''; + _secret_key = ''; + _blinding = ''; + _originChainId?: string; + + serialize(): string { + return [ + this._curve, + this._backend, + this.amount, + this.chainId, + this.blinding, + this.getKeypair().getPubKey().slice(2), + this.getKeypair().getEncryptionKey()?.slice(2), + this.getKeypair().privkey?.slice(2), + this.index.toString(), + ].join('&'); + } + + /** + * @param utxoString - A string representation of the parts that make up a utxo. + * - All values are represented as BigEndian, hex-encoded strings unless indicated otherwise. + * - Optional values are represented as the empty string if not present, + * meaning the split call will always be an array of length "parts". + * + * parts[0] - Curve value, e.g. Bn254, Bls381, Ed25519, etc. value represented as string. + * parts[1] - Backend value, e.g. arkworks or circom. value represented as string. + * parts[2] - Amount of atomic units, e.g. ETH in wei amounts or DOT in 10^12 decimals. value represented as uint. + * parts[3] - TypedChainId, the hex value of the calculated typed chain id + * parts[4] - Blinding, secret random value + * parts[5] - PublicKey, the "publicKey = hash(privateKey)" value which indicates ownership for a utxo. + * parts[6] Optional - EncryptionKey, the public key of "publicKey = encryptionScheme(privateKey)" value used for messaging. + * parts[7] Optional - PrivateKey, the secret key component correlated to the above values. + * parts[8] Optional - Index, the leaf index if the utxo has been inserted in a merkle tree + * @returns The Utxo object implementation of a Utxo. + */ + static deserialize(utxoString: string): Utxo { + const utxo = new Utxo(); + const parts = utxoString.split('&'); + + utxo._curve = 'Bn254'; + utxo._backend = 'Circom'; + utxo._amount = parts[2]; + utxo._chainId = parts[3]; + utxo._blinding = parts[4]; + utxo._pubkey = parts[5]; + const maybeEncryptionKey = parts[6]; + const maybeSecretKey = parts[7]; + const maybeIndex = parts[8]; + + if (maybeSecretKey.length === 64) { + utxo.setKeypair(new Keypair('0x' + maybeSecretKey)); + } else { + if (maybeEncryptionKey.length === 64) { + utxo.setKeypair(Keypair.fromString('0x' + utxo._pubkey + maybeEncryptionKey)); + } else { + utxo.setKeypair(Keypair.fromString('0x' + utxo._pubkey)); + } + } + + if (maybeIndex.length > 0) { + utxo._index = Number(maybeIndex); + } + + return utxo; + } + + static generateUtxo(input: UtxoGenInput): Utxo { + const utxo = new Utxo(); + + // Required parameters + utxo._amount = input.amount; + utxo._chainId = input.chainId; + utxo._curve = input.curve; + utxo._backend = input.backend; + + // Optional parameters + utxo._index = input.index ? Number(input.index) : 0; + + if (input.keypair) { + utxo.setKeypair(input.keypair); + } else { + // Populate the _pubkey and _secret_key values with + // the random default keypair + utxo.setKeypair(utxo.getKeypair()); + } + + utxo._blinding = input.blinding + ? u8aToHex(input.blinding).slice(2) + : toFixedHex(randomBN(31)).slice(2); + utxo.setOriginChainId(input.originChainId); + + return utxo; + } + + /** + * Encrypt UTXO data using the current keypair. + * This is used in the externalDataHash calculations so the funds for this deposit + * can only be spent by the owner of `this.keypair`. + * + * @returns `0x`-prefixed hex string with data + */ + encrypt() { + if (!this.getKeypair().getEncryptionKey()) { + throw new Error('Must have a configured encryption key on the keypair to encrypt the utxo'); + } + + const bytes = Buffer.concat([ + toBuffer(BigNumber.from(this._chainId), 8), + toBuffer(BigNumber.from(this._amount), 32), + toBuffer(BigNumber.from('0x' + this._blinding), 32), + ]); + + return this.getKeypair().encrypt(bytes); + } + + /** + * Decrypt a UTXO + * + * @param keypair - keypair used to decrypt + * @param data - hex string with data + * @returns a UTXO object + */ + static decrypt(keypair: Keypair, data: string) { + const buf = keypair.decrypt(data); + + if (buf.length !== 72) { + throw new Error('malformed utxo encryption'); + } + + const utxo = Utxo.generateUtxo({ + amount: BigNumber.from('0x' + buf.subarray(8, 8 + 32).toString('hex')).toString(), + backend: 'Circom', + blinding: hexToU8a(toFixedHex('0x' + buf.subarray(8 + 32, 8 + 64).toString('hex'))), + chainId: BigNumber.from('0x' + buf.subarray(0, 8).toString('hex')).toString(), + curve: 'Bn254', + keypair, + }); + + return utxo; + } + + get keypair(): Keypair { + return this._keypair; + } + + set keypair(keypair: Keypair) { + this._keypair = keypair; + } + + get amount(): string { + return this._amount; + } + + set amount(amount: string) { + this._amount = amount; + } + + get blinding(): string { + return this._blinding; + } + + set blinding(blinding: string) { + this._blinding = blinding; + } + + get chainId(): string { + return this._chainId; + } + + set chainId(chainId: string) { + this._chainId = chainId; + } + + get originChainId(): string | undefined { + return this._originChainId; + } + + set originChainId(originChainId: string | undefined) { + this._originChainId = originChainId; + } + + /** + * Returns commitment for this UTXO + * + * @returns the poseidon hash of [chainId, amount, pubKey, blinding] + */ + get commitment(): Uint8Array { + const hash = poseidon([ + this._chainId, + this._amount, + '0x' + this._pubkey, + '0x' + this._blinding, + ]); + + return hexToU8a(BigNumber.from(hash).toHexString()); + } + + /** + * @returns the index configured on this UTXO. Output UTXOs generated + * before they have been inserted in a tree. + * + */ + get index(): number { + return this._index ?? 0; + } + + set index(index: number) { + this._index = index; + } + + /** + * @returns the nullifier: hash of [commitment, index, signature] as decimal string + * where signature = hash([secret key, commitment, index]) + */ + get nullifier(): string { + // If the amount of the UTXO is zero, then the nullifier is not important. + // Return a 'dummy' value that will satisfy the circuit + // Enforce index on the UTXO if there is an amount greater than zero + if (!this.getKeypair() || !this.getKeypair().privkey) { + throw new Error('Cannot create nullifier, keypair with private key not configured'); + } + + const x = poseidon([ + u8aToHex(this.commitment), + this.index > 0 ? this.index : 0, + // The following parameter is the 'ownership hash', a portion of the nullifier that enables + // compliance, and ties a utxo to a particular keypair. + poseidon([this.getKeypair().privkey, u8aToHex(this.commitment), this.index]), + ]); + + return toFixedHex(x).slice(2); + } + + get public_key(): string { + return this._pubkey; + } + + /** + * @returns the secret_key AKA private_key used in the nullifier. + * this value is used to derive the public_key for the commitment. + */ + get secret_key(): string { + return this._secret_key; + } + + set secret_key(secret: string) { + this._secret_key = secret; + } + + getKeypair(): Keypair { + return this._keypair; + } + + setKeypair(keypair: Keypair): void { + this._pubkey = keypair.getPubKey().slice(2); + + if (keypair.privkey) { + this._secret_key = keypair.privkey.slice(2); + } + + this._keypair = keypair; + } + + setOriginChainId(originChainId: string | undefined) { + this._originChainId = originChainId; + } + + setIndex(val: number): void { + this.index = val; + } +} diff --git a/packages/utils/src/types.ts b/packages/utils/src/types.ts deleted file mode 100644 index bbd4eb3b4..000000000 --- a/packages/utils/src/types.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { BigNumberish } from 'ethers'; - -export type ZkComponents = { - wasm: Buffer; - zkey: Uint8Array; - witnessCalculator: any; -}; - -export interface RootInfo { - merkleRoot: BigNumberish; - chainId: BigNumberish; -} diff --git a/packages/utils/src/utils.ts b/packages/utils/src/utils.ts index b4f2e9e9c..87f705411 100644 --- a/packages/utils/src/utils.ts +++ b/packages/utils/src/utils.ts @@ -1,45 +1,73 @@ -/* eslint-disable camelcase */ -/* eslint-disable sort-keys */ -import { BigNumber } from 'ethers'; -import { groth16 } from 'snarkjs'; - -import { toFixedHex } from '@webb-tools/sdk-core'; - -export const ZERO_BYTES32 = '0x0000000000000000000000000000000000000000000000000000000000000000'; -export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; - -export const FIELD_SIZE = BigNumber.from( - '21888242871839275222246405745257275088548364400416034343698204186575808495617' -); - -export type Proof = { - pi_a: string[3]; - pi_b: Array; - pi_c: string[3]; - protocol: string; - curve: string; +import crypto from 'crypto'; +import { BigNumber, BigNumberish, BytesLike, ethers } from 'ethers'; +import { FIELD_SIZE } from './protocol'; + +import EC from 'elliptic'; + +export const median = (arr: number[]): number => { + const s = [...arr].sort((a, b) => a - b); + const mid = Math.floor(s.length / 2); + + return s.length % 2 === 0 ? (s[mid - 1] + s[mid]) / 2 : s[mid]; +}; + +export const mean = (arr: number[]) => arr.reduce((p, c) => p + c, 0) / arr.length; +export const max = (arr: number[]) => arr.reduce((a, b) => (a > b ? a : b)); +export const min = (arr: number[]) => arr.reduce((a, b) => (a <= b ? a : b)); + +/** Generate random number of specified byte length */ +export const randomBN = (nbytes = 31) => BigNumber.from(crypto.randomBytes(nbytes)); + +export const randomFieldElement = (nbytes = 32) => { + const fieldElt = randomBN(nbytes).mod(FIELD_SIZE); + return Uint8Array.from(Buffer.from(fieldElt.toHexString())); +}; + +export const toHex = ( + covertThis: ethers.utils.BytesLike | number | bigint, + padding: number +): string => { + return ethers.utils.hexZeroPad(ethers.utils.hexlify(covertThis), padding); }; -export type VAnchorProofInputs = { - roots: string[]; - chainID: string; - inputNullifier: string[]; - outputCommitment: string[]; - publicAmount: string; - extDataHash: string; - - // data for 2 transaction inputs - inAmount: string[]; - inPrivateKey: string[]; - inBlinding: string[]; - inPathIndices: number[][]; - inPathElements: number[][]; - - // data for 2 transaction outputs - outChainID: string; - outAmount: string[]; - outPubkey: string[]; - outBlinding: string[]; +/** BigNumber to hex string of specified length */ +export function toFixedHex(number: BigNumberish, length = 32): string { + let result = + '0x' + + (number instanceof Buffer + ? number.toString('hex') + : BigNumber.from(number).toHexString().replace('0x', '') + ).padStart(length * 2, '0'); + + if (result.indexOf('-') > -1) { + result = '-' + result.replace('-', ''); + } + + return result; +} + +/** Pad the bigint to 256 bits (32 bytes) */ +export function p256(n: bigint) { + let nstr = BigInt(n).toString(16); + + while (nstr.length < 64) { + nstr = '0' + nstr; + } + + nstr = `"0x${nstr}"`; + + return nstr; +} + +/** Convert value into buffer of specified byte length */ +export const toBuffer = (value: BigNumberish, length: number) => { + return Buffer.from( + BigNumber.from(value) + .toHexString() + .slice(2) + .padStart(length * 2, '0'), + 'hex' + ); }; /** @@ -53,18 +81,28 @@ export const getChainIdType = (chainID: number = 31337): number => { return Number(BigInt(chainIdType)); }; -export default function verifyProof( - verificationKey: any, - { proof, publicSignals }: any -): Promise { - return groth16.verify( - verificationKey, - [ - publicSignals.merkleRoot, - publicSignals.nullifierHash, - publicSignals.signalHash, - publicSignals.externalNullifier, - ], - proof - ); -} +export const signMessage = (wallet: ethers.Wallet, data: BytesLike) => { + // eslint-disable-next-line new-cap + const ec = new EC.ec('secp256k1'); + const key = ec.keyFromPrivate(wallet.privateKey.slice(2), 'hex'); + const hash = ethers.utils.keccak256(data); + const hashedData = ethers.utils.arrayify(hash); + const signature = key.sign(hashedData); + const expandedSig = { + r: '0x' + signature.r.toString('hex'), + s: '0x' + signature.s.toString('hex'), + v: signature.recoveryParam + 27, + }; + let sig; + + // Transaction malleability fix if s is too large (Bitcoin allows it, Ethereum rejects it) + try { + sig = ethers.utils.joinSignature(expandedSig); + } catch (_) { + expandedSig.s = '0x' + BigNumber.from(ec.curve.n).sub(signature.s).toHexString(); + expandedSig.v = expandedSig.v === 27 ? 28 : 27; + sig = ethers.utils.joinSignature(expandedSig); + } + + return sig; +}; diff --git a/packages/utils/tsconfig.build.json b/packages/utils/tsconfig.build.json index 368ccdee7..48006d517 100644 --- a/packages/utils/tsconfig.build.json +++ b/packages/utils/tsconfig.build.json @@ -4,7 +4,7 @@ "outDir": "./dist/", "baseUrl": "." }, - "exclude": [], + "exclude": ["./src/**/*.spec.ts"], "extends": "../../tsconfig.build.json", "include": [ "./src/**/*.ts", diff --git a/packages/vbridge/package.json b/packages/vbridge/package.json index ea6ca19b2..3f698d8d1 100644 --- a/packages/vbridge/package.json +++ b/packages/vbridge/package.json @@ -12,7 +12,6 @@ "@webb-tools/anchors": "^1.0.4", "@webb-tools/contracts": "^1.0.4", "@webb-tools/interfaces": "^1.0.4", - "@webb-tools/sdk-core": "0.1.4-126", "@webb-tools/tokens": "^1.0.4", "@webb-tools/utils": "^1.0.4", "ethers": "5.7.0" diff --git a/packages/vbridge/src/SignatureBridgeSide.ts b/packages/vbridge/src/SignatureBridgeSide.ts index 5bd2620ae..a7316c5fe 100644 --- a/packages/vbridge/src/SignatureBridgeSide.ts +++ b/packages/vbridge/src/SignatureBridgeSide.ts @@ -7,7 +7,7 @@ import { Deployer } from '@webb-tools/create2-utils'; import { IVAnchor, IBridgeSide, Proposal } from '@webb-tools/interfaces'; import { TreasuryHandler } from '@webb-tools/tokens'; import { getChainIdType } from '@webb-tools/utils'; -import { signMessage, toHex } from '@webb-tools/sdk-core'; +import { signMessage, toHex } from '@webb-tools/utils'; type SystemSigningFn = (data: any) => Promise; diff --git a/packages/vbridge/src/VBridge.ts b/packages/vbridge/src/VBridge.ts index d76594e8d..7e49112e5 100644 --- a/packages/vbridge/src/VBridge.ts +++ b/packages/vbridge/src/VBridge.ts @@ -9,7 +9,7 @@ import { import { GovernorConfig, DeployerConfig, IVAnchor } from '@webb-tools/interfaces'; import { AnchorHandler, PoseidonHasher, VAnchor, WebbContracts } from '@webb-tools/anchors'; import { hexToU8a, u8aToHex, getChainIdType, ZkComponents } from '@webb-tools/utils'; -import { CircomUtxo, Utxo } from '@webb-tools/sdk-core'; +import { Utxo } from '@webb-tools/utils'; import { Verifier } from '@webb-tools/anchors'; import { SignatureBridgeSide } from './SignatureBridgeSide'; @@ -438,7 +438,7 @@ export class VBridge { // Create dummy UTXOs to satisfy the circuit while (inputs.length !== 2 && inputs.length < 16) { inputs.push( - await CircomUtxo.generateUtxo({ + Utxo.generateUtxo({ curve: 'Bn254', backend: 'Circom', chainId: chainId.toString(), diff --git a/scripts/evm/bridgeActions/changeGovernor.ts b/scripts/evm/bridgeActions/changeGovernor.ts deleted file mode 100644 index 4617aeebf..000000000 --- a/scripts/evm/bridgeActions/changeGovernor.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { ethers } from 'ethers'; -import { SignatureBridgeSide } from '@webb-tools/vbridge'; - -export async function changeGovernor( - bridgeAddress: string, - newGovernor: string, - currentGovernor: ethers.Wallet -) { - const bridgeInstance = await SignatureBridgeSide.connectMocked(bridgeAddress, currentGovernor); - const refreshNonce = await bridgeInstance.contract.refreshNonce(); - const tx = await bridgeInstance.transferOwnership(newGovernor, refreshNonce + 1); - const receipt = await tx.wait(); - console.log(receipt); -} diff --git a/scripts/evm/bridgeActions/transactWrapNative.ts b/scripts/evm/bridgeActions/transactWrapNative.ts deleted file mode 100644 index a885b5763..000000000 --- a/scripts/evm/bridgeActions/transactWrapNative.ts +++ /dev/null @@ -1,65 +0,0 @@ -// @ts-nocheck -require('dotenv').config(); -import { ethers } from 'ethers'; -import { VAnchor } from '@webb-tools/anchors'; -import path from 'path'; -import { - hexToU8a, - fetchComponentsFromFilePaths, - getChainIdType, - vanchorFixtures, -} from '@webb-tools/utils'; -import { CircomUtxo, Keypair, randomBN, Utxo } from '@webb-tools/sdk-core'; - -export async function transactWrapNative(anchorAddress: string, sender: ethers.Signer) { - const zkComponentsSmall = await vanchorFixtures[28](); - const zkComponentsLarge = await vanchorFixtures[168](); - const anchor = await VAnchor.connect(anchorAddress, zkComponentsSmall, zkComponentsLarge, sender); - const sourceChainId = getChainIdType(5001); - const randomKeypair = new Keypair(); - - const utxo = await CircomUtxo.generateUtxo({ - curve: 'Bn254', - backend: 'Circom', - chainId: sourceChainId.toString(), - originChainId: sourceChainId.toString(), - amount: '1000000000000000', - keypair: randomKeypair, - }); - - // Build up the inputs for proving manager - const dummyOutputUtxo = await CircomUtxo.generateUtxo({ - curve: 'Bn254', - backend: 'Circom', - chainId: sourceChainId.toString(), - originChainId: sourceChainId.toString(), - amount: '0', - keypair: randomKeypair, - }); - const inputs: Utxo[] = []; - const outputs: [Utxo, Utxo] = [utxo, dummyOutputUtxo]; - - while (inputs.length !== 2 && inputs.length < 16) { - inputs.push( - await CircomUtxo.generateUtxo({ - curve: 'Bn254', - backend: 'Circom', - chainId: sourceChainId.toString(), - originChainId: sourceChainId.toString(), - amount: '0', - blinding: hexToU8a(randomBN(31).toHexString()), - keypair: randomKeypair, - }) - ); - } - - const tx = await anchor.transactWrap( - '0x0000000000000000000000000000000000000000', - inputs, - outputs, - '0', - '0x0000000000000000000000000000000000000000', - '0x0000000000000000000000000000000000000000', - {} - ); -} diff --git a/scripts/evm/deployments/LocalEvmVBridge.ts b/scripts/evm/deployments/LocalEvmVBridge.ts deleted file mode 100755 index b3deaba9d..000000000 --- a/scripts/evm/deployments/LocalEvmVBridge.ts +++ /dev/null @@ -1,455 +0,0 @@ -// This a simple script to start two local testnet chains and deploy the contracts on both of them -import path from 'path'; -import readline from 'readline'; -import { ethers } from 'ethers'; -import { FungibleTokenWrapper, MintableToken } from '@webb-tools/tokens'; -import { - fetchComponentsFromFilePaths, - getChainIdType, - vanchorFixtures, - ZkComponents, -} from '@webb-tools/utils'; -import { CircomUtxo } from '@webb-tools/sdk-core'; -import ECPairFactory, { ECPairAPI, TinySecp256k1Interface } from 'ecpair'; -import { LocalEvmChain } from '@webb-tools/evm-test-utils'; - -const tinysecp: TinySecp256k1Interface = require('tiny-secp256k1'); -const ECPair: ECPairAPI = ECPairFactory(tinysecp); - -export type GanacheAccounts = { - balance: string; - secretKey: string; -}; - -export function ethAddressFromUncompressedPublicKey(publicKey: `0x${string}`): `0x${string}` { - const pubKeyHash = ethers.utils.keccak256(publicKey); // we hash it. - const address = ethers.utils.getAddress(`0x${pubKeyHash.slice(-40)}`); // take the last 20 bytes and convert it to an address. - return address as `0x${string}`; -} - -/** - * Encode function Signature in the Solidity format. - */ -export function encodeFunctionSignature(func: string): `0x${string}` { - return ethers.utils.keccak256(ethers.utils.toUtf8Bytes(func)).slice(0, 10) as `0x${string}`; -} - -export function uncompressPublicKey(compressed: string): `0x${string}` { - const dkgPubKey = ECPair.fromPublicKey(Buffer.from(compressed.slice(2), 'hex'), { - compressed: false, - }).publicKey.toString('hex'); - // now we remove the `04` prefix byte and return it. - return `0x${dkgPubKey.slice(2)}` as `0x${string}`; -} - -// This function mints tokens of the wrappable and governed tokens on the network -// wrappableTokens: mapping from chainIdType to admin configured signer instance of MintableToken -// governedTokens: mapping from chainIdType to admin configured signer instance of MintableToken -// addresses: the list of addresses to fund -export async function fundAccounts( - wrappableTokens: Record, - governedTokens: Record, - addresses: string[] -) { - // For each of the chainIdTypes, mint the wrappable and governed tokens to all of the addresses - const chainIdTypes = Object.keys(governedTokens); - - for (const chainIdType of chainIdTypes) { - for (const address of addresses) { - await wrappableTokens[chainIdType].mintTokens(address, ethers.utils.parseEther('1000')); - await governedTokens[chainIdType].mintTokens(address, ethers.utils.parseEther('1000')); - } - } -} - -export async function fetchZkFixtures(chains: any[]) { - const isEightSided = Object.keys(chains).length > 2; - - let zkComponentsSmall: ZkComponents; - let zkComponentsLarge: ZkComponents; - - if (isEightSided) { - zkComponentsSmall = await vanchorFixtures[28](); - zkComponentsLarge = await vanchorFixtures[168](); - } else { - zkComponentsSmall = await vanchorFixtures[22](); - zkComponentsLarge = await vanchorFixtures[162](); - } - - return { - zkComponentsSmall, - zkComponentsLarge, - }; -} - -async function main() { - const relayerPrivateKey = '0x0000000000000000000000000000000000000000000000000000000000000001'; - const senderPrivateKey = '0x0000000000000000000000000000000000000000000000000000000000000002'; - - const chainA = await LocalEvmChain.init('Hermes', 5001, [ - { - balance: ethers.utils.parseEther('1000').toHexString(), - secretKey: relayerPrivateKey, - }, - { - balance: ethers.utils.parseEther('1000').toHexString(), - secretKey: senderPrivateKey, - }, - { - balance: ethers.utils.parseEther('1000').toHexString(), - secretKey: '0xc0d375903fd6f6ad3edafc2c5428900c0757ce1da10e5dd864fe387b32b91d7e', - }, - ]); - const chainB = await LocalEvmChain.init('Athena', 5002, [ - { - balance: ethers.utils.parseEther('1000').toHexString(), - secretKey: relayerPrivateKey, - }, - { - balance: ethers.utils.parseEther('1000').toHexString(), - secretKey: senderPrivateKey, - }, - { - balance: ethers.utils.parseEther('1000').toHexString(), - secretKey: '0xc0d375903fd6f6ad3edafc2c5428900c0757ce1da10e5dd864fe387b32b91d7e', - }, - ]); - const chainC = await LocalEvmChain.init('Demeter', 5003, [ - { - balance: ethers.utils.parseEther('1000').toHexString(), - secretKey: relayerPrivateKey, - }, - { - balance: ethers.utils.parseEther('1000').toHexString(), - secretKey: senderPrivateKey, - }, - { - balance: ethers.utils.parseEther('1000').toHexString(), - secretKey: '0xc0d375903fd6f6ad3edafc2c5428900c0757ce1da10e5dd864fe387b32b91d7e', - }, - ]); - - console.log('Chain A typedChainId: ', chainA.typedChainId); - console.log('Chain B typedChainId: ', chainB.typedChainId); - console.log('Chain C typedChainId: ', chainC.typedChainId); - - const chainAWallet = new ethers.Wallet(relayerPrivateKey, chainA.provider()); - const chainBWallet = new ethers.Wallet(relayerPrivateKey, chainB.provider()); - const chainCWallet = new ethers.Wallet(relayerPrivateKey, chainC.provider()); - console.log('Before random transfers'); - // do a random transfer on chainA to a random address - // do it on chainB twice. - // so we do have different nonce for that account. - let tx = await chainAWallet.sendTransaction({ - to: '0x0000000000000000000000000000000000000000', - value: ethers.utils.parseEther('0.001'), - }); - await tx.wait(); - tx = await chainBWallet.sendTransaction({ - to: '0x0000000000000000000000000000000000000000', - value: ethers.utils.parseEther('0.001'), - }); - await tx.wait(); - tx = await chainBWallet.sendTransaction({ - to: '0x0000000000000000000000000000000000000000', - value: ethers.utils.parseEther('0.001'), - }); - await tx.wait(); - console.log('After random transfers'); - // Deploy the token on chainA - const chainAToken = await chainA.deployToken('ChainA', 'webbA', chainAWallet); - // Deploy the token on chainB - const chainBToken = await chainB.deployToken('ChainB', 'webbB', chainBWallet); - // Deploy the token on chainC - const chainCToken = await chainC.deployToken('ChainC', 'webbC', chainCWallet); - console.log('After deploy WEBB tokens'); - // Deploy the signature bridge. - let zkComponents = await fetchZkFixtures([chainA, chainB, chainC]); - const signatureBridge = await LocalEvmChain.deployVBridge( - [chainA, chainB, chainC], - [chainAToken, chainBToken, chainCToken], - [chainAWallet, chainBWallet, chainCWallet], - zkComponents.zkComponentsSmall, - zkComponents.zkComponentsLarge - ); - console.log('After deploying VBridges'); - // get chainA bridge - const chainASignatureBridge = signatureBridge.getVBridgeSide(chainA.typedChainId)!; - console.log('sigBridgeA address: ', chainASignatureBridge.contract.address); - // get chainB bridge - const chainBSignatureBridge = signatureBridge.getVBridgeSide(chainB.typedChainId)!; - console.log('sigBridgeB address: ', chainBSignatureBridge.contract.address); - // get chainC bridge - const chainCSignatureBridge = signatureBridge.getVBridgeSide(chainC.typedChainId)!; - console.log('sigBridgeC address: ', chainCSignatureBridge.contract.address); - - // get the anchor on chainA - const chainASignatureAnchor = signatureBridge.getVAnchor(chainA.typedChainId)!; - await chainASignatureAnchor.setSigner(chainAWallet); - - const chainAHandler = await chainASignatureAnchor.getHandler(); - console.log('Chain A Handler address: ', chainAHandler); - - // get the anchor on chainB - const chainBSignatureAnchor = signatureBridge.getVAnchor(chainB.typedChainId)!; - await chainBSignatureAnchor.setSigner(chainBWallet); - - const chainBHandler = await chainBSignatureAnchor.getHandler(); - console.log('Chain B Handler address: ', chainBHandler); - - // get the anchor on chainC - const chainCSignatureAnchor = signatureBridge.getVAnchor(chainC.typedChainId)!; - await chainCSignatureAnchor.setSigner(chainCWallet); - - const chainCHandler = await chainCSignatureAnchor.getHandler(); - console.log('Chain C Handler address: ', chainCHandler); - - // approve token spending - const webbASignatureTokenAddress = signatureBridge.getWebbTokenAddress(chainA.typedChainId)!; - - const webbASignatureToken = await MintableToken.tokenFromAddress( - webbASignatureTokenAddress, - chainAWallet - ); - tx = await webbASignatureToken.approveSpending( - chainASignatureAnchor.contract.address, - ethers.utils.parseEther('1000') - ); - await tx.wait(); - await webbASignatureToken.mintTokens(chainAWallet.address, ethers.utils.parseEther('1000')); - - const webbBSignatureTokenAddress = signatureBridge.getWebbTokenAddress(chainB.typedChainId)!; - console.log('webbBTokenAddress: ', webbBSignatureTokenAddress); - - const webbBSignatureToken = await MintableToken.tokenFromAddress( - webbBSignatureTokenAddress, - chainBWallet - ); - tx = await webbBSignatureToken.approveSpending( - chainBSignatureAnchor.contract.address, - ethers.utils.parseEther('1000') - ); - await tx.wait(); - await webbBSignatureToken.mintTokens(chainBWallet.address, ethers.utils.parseEther('1000')); - - const webbCSignatureTokenAddress = signatureBridge.getWebbTokenAddress(chainC.typedChainId)!; - - const webbCSignatureToken = await MintableToken.tokenFromAddress( - webbCSignatureTokenAddress, - chainCWallet - ); - tx = await webbCSignatureToken.approveSpending( - chainCSignatureAnchor.contract.address, - ethers.utils.parseEther('1000') - ); - await tx.wait(); - await webbCSignatureToken.mintTokens(chainCWallet.address, ethers.utils.parseEther('1000')); - - // stop the server on Ctrl+C or SIGINT singal - process.on('SIGINT', () => { - chainA.stop(); - chainB.stop(); - chainC.stop(); - }); - - // mint wrappable and governed tokens to pre-funded accounts - await fundAccounts( - { - [chainA.typedChainId]: chainAToken, - [chainB.typedChainId]: chainBToken, - [chainC.typedChainId]: chainCToken, - }, - { - [chainA.typedChainId]: webbASignatureToken, - [chainB.typedChainId]: webbBSignatureToken, - [chainC.typedChainId]: webbCSignatureToken, - }, - [ - '0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf', - '0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF', - '0xd644f5331a6F26A7943CEEbB772e505cDDd21700', - ] - ); - - printAvailableCommands(); - - // setup readline - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - }); - - rl.on('line', async (cmdRaw) => { - const cmd = cmdRaw.trim(); - - if (cmd.startsWith('variable deposit on chain a')) { - const utxo = await CircomUtxo.generateUtxo({ - curve: 'Bn254', - backend: 'Circom', - chainId: getChainIdType(5002).toString(), - originChainId: getChainIdType(5001).toString(), - amount: '10000000000', - }); - - await signatureBridge.transact([], [utxo], '0', '0', '0', '0', '', chainAWallet); - return; - } - - if (cmd.startsWith('transfer ownership to governor')) { - const compressedKey = cmd.split('"')[1]; - const uncompressedKey = uncompressPublicKey(compressedKey); - const governorAddress = ethAddressFromUncompressedPublicKey(uncompressedKey); - - let tx = await chainASignatureBridge.transferOwnership(governorAddress, 0); - await tx.wait(); - tx = await chainBSignatureBridge.transferOwnership(governorAddress, 0); - await tx.wait(); - tx = await chainCSignatureBridge.transferOwnership(governorAddress, 0); - await tx.wait(); - console.log('ownership transferred!'); - return; - } - - if (cmd.startsWith('add wrappable token to a')) { - const governedToken = await FungibleTokenWrapper.connect( - webbASignatureTokenAddress, - chainAWallet - ); - const newWrappableToken = await MintableToken.createToken( - 'localETH', - 'localETH', - chainAWallet - ); - // address of private key - await newWrappableToken.mintTokens( - '0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF', - '100000000000000000000' - ); - await chainASignatureBridge.executeAddTokenProposalWithSig( - governedToken, - newWrappableToken.contract.address - ); - console.log('wrappable token added to hermes'); - return; - } - - if (cmd.startsWith('mint wrappable token on a')) { - const address = cmd.split('"')[1]; - await chainAToken.mintTokens(address, '100000000000000000000'); - console.log('minted tokens'); - return; - } - - if (cmd.startsWith('mint wrappable token on b')) { - const address = cmd.split('"')[1]; - await chainBToken.mintTokens(address, '100000000000000000000'); - console.log('minted tokens'); - return; - } - - if (cmd.startsWith('mint governed token on a')) { - const address = cmd.split('"')[1]; - await webbASignatureToken.mintTokens(address, '100000000000000000000'); - console.log('minted tokens'); - return; - } - - if (cmd.startsWith('mint governed token on b')) { - const address = cmd.split('"')[1]; - await webbBSignatureToken.mintTokens(address, '100000000000000000000'); - console.log('minted tokens'); - return; - } - - if (cmd.startsWith('print config')) { - console.log('ChainA signature bridge (Hermes): ', chainASignatureBridge.contract.address); - const chainAHandler = await chainASignatureAnchor.getHandler(); - console.log('Chain A Handler address: ', chainAHandler); - console.log('ChainA variable anchor (Hermes): ', chainASignatureAnchor.contract.address); - console.log('ChainAToken: ', chainAToken.contract.address); - console.log('ChainA Webb token (Hermes): ', webbASignatureToken.contract.address); - console.log(' --- --- --- --- --- --- --- --- --- --- --- --- ---'); - console.log('ChainB signature bridge (Athena): ', chainBSignatureBridge.contract.address); - const chainBHandler = await chainBSignatureAnchor.getHandler(); - console.log('Chain B Handler address: ', chainBHandler); - console.log('ChainB variable anchor (Athena): ', chainBSignatureAnchor.contract.address); - console.log('ChainBToken: ', chainBToken.contract.address); - console.log('ChainB token Webb (Athena): ', webbBSignatureToken.contract.address); - console.log(' --- --- --- --- --- --- --- --- --- --- --- --- ---'); - console.log('ChainC signature bridge (Demeter): ', chainCSignatureBridge.contract.address); - const chainCHandler = await chainCSignatureAnchor.getHandler(); - console.log('Chain C Handler address: ', chainCHandler); - console.log('ChainC variable anchor (Demeter): ', chainCSignatureAnchor.contract.address); - console.log('ChainCToken: ', chainCToken.contract.address); - console.log('ChainC token Webb (Demeter): ', webbCSignatureToken.contract.address); - - console.log('\n'); - return; - } - - if (cmd.startsWith('print root on chain a')) { - console.log('Root on chain A (signature), please wait...'); - const root2 = await chainASignatureAnchor.contract.getLastRoot(); - const latestNeighborRoots2 = await chainASignatureAnchor.contract.getLatestNeighborRoots(); - console.log('Root on chain A (signature): ', root2); - console.log('Latest neighbor roots on chain A (signature): ', latestNeighborRoots2); - return; - } - - if (cmd.startsWith('print root on chain b')) { - console.log('Root on chain B (signature), please wait...'); - const root2 = await chainBSignatureAnchor.contract.getLastRoot(); - const latestNeighborRoots2 = await chainBSignatureAnchor.contract.getLatestNeighborRoots(); - console.log('Root on chain B (signature): ', root2); - console.log('Latest neighbor roots on chain B (signature): ', latestNeighborRoots2); - return; - } - - if (cmd.startsWith('print governor on chain a')) { - const governor = chainASignatureBridge.governor; - console.log('governor in chainASigBridge class is: ', (governor as ethers.Wallet).address); - const walletAddress = await chainASignatureBridge.contract.governor(); - console.log('governor in contract is: ', walletAddress); - return; - } - - if (cmd.startsWith('print governor on chain b')) { - const governor = chainBSignatureBridge.governor; - console.log('governor in chainASigBridge class is: ', (governor as ethers.Wallet).address); - const walletAddress = await chainBSignatureBridge.contract.governor(); - console.log('governor in contract is: ', walletAddress); - return; - } - - if (cmd === 'exit') { - // shutdown the servers - await chainA.stop(); - await chainB.stop(); - await chainC.stop(); - rl.close(); - return; - } - - console.log('Unknown command: ', cmd); - printAvailableCommands(); - }); -} - -function printAvailableCommands() { - console.log('Available commands:'); - console.log(' variable deposit on chain a'); - console.log(' transfer ownership to governor ""'); - console.log(' add wrappable token to a'); - console.log(' mint wrappable token on a to "
"'); - console.log(' mint wrappable token on b to "
"'); - console.log(' mint governed token on a to "
"'); - console.log(' mint governed token on b to "
"'); - console.log(' print config'); - console.log(' print root on chain a'); - console.log(' print root on chain b'); - console.log(' print governor on chain a'); - console.log(' print governor on chain b'); - console.log(' exit'); -} - -main().catch(console.error); diff --git a/scripts/evm/deployments/VBridge8Side.ts b/scripts/evm/deployments/VBridge8Side.ts deleted file mode 100644 index 1ced1020c..000000000 --- a/scripts/evm/deployments/VBridge8Side.ts +++ /dev/null @@ -1,190 +0,0 @@ -import { VBridge } from '@webb-tools/vbridge'; -import { FungibleTokenWrapper } from '@webb-tools/tokens'; -import { getChainIdType, vanchorFixtures } from '@webb-tools/utils'; -import { DeployerConfig } from '@webb-tools/interfaces'; -import path from 'path'; -import { - chainIdTypeGoerli, - chainIdTypeOptimism, - chainIdTypeArbitrum, - chainIdTypePolygon, - chainIdTypeMoonbase, - walletGoerli, - walletOptimism, - walletArbitrum, - walletPolygon, - walletMoonbase, - chainIdTypeHermes, - chainIdTypeAthena, - chainIdTypeDemeter, - walletHermes, - walletAthena, - walletDemeter, - chainIdTypeSepolia, - walletSepolia, - chainIdTypeAurora, -} from '../ethersGovernorWallets'; -import { EvmLinkedAnchor, ProposalSigningBackend } from '@webb-tools/test-utils'; -import { ContractConfig, getEvmChainConfig, writeEvmChainConfig } from './utils'; -import { zip } from 'itertools'; -import fs from 'fs'; -import { - EndPointConfig, - goerliEndPoints, - moonbaseEndPoints, - optimismEndPoints, - polygonEndPoints, - sepoliaEndPoints, -} from './endPoints'; -import { VAnchorTree } from '@webb-tools/contracts'; - -async function deploySignatureVBridge( - tokens: Record, - deployers: DeployerConfig -): Promise> { - let assetRecord: Record = {}; - let chainIdsArray: number[] = []; - let existingWebbTokens = new Map(); - let governorConfig: Record = {}; - - for (const chainIdType of Object.keys(deployers)) { - assetRecord[chainIdType] = tokens[chainIdType]; - chainIdsArray.push(Number(chainIdType)); - governorConfig[Number(chainIdType)] = await deployers[chainIdType].getAddress(); - existingWebbTokens[chainIdType] = null; - console.log(tokens[chainIdType]); - } - - const bridgeInput = { - vAnchorInputs: { - asset: assetRecord, - }, - chainIds: chainIdsArray, - webbTokens: existingWebbTokens, - }; - - console.log(bridgeInput); - - const zkComponentsSmall = await vanchorFixtures[28](); - const zkComponentsLarge = await vanchorFixtures[168](); - - console.log(governorConfig); - - return VBridge.deployVariableAnchorBridge( - bridgeInput, - deployers, - governorConfig, - zkComponentsSmall, - zkComponentsLarge - ); -} - -async function run() { - const deployers: DeployerConfig = { - // [chainIdTypeGoerli]: walletGoerli, - // [chainIdTypeSepolia]: walletSepolia, - // [chainIdTypeOptimism]: walletOptimism, - [chainIdTypePolygon]: walletPolygon, - // [chainIdTypeMoonbase]: walletMoonbase, - // [chainIdTypeArbitrum]: walletArbitrum, - // [chainIdTypeHermes]: walletHermes, - // [chainIdTypeAthena]: walletAthena, - // [chainIdTypeDemeter]: walletDemeter - }; - - const tokens: Record = { - // [chainIdTypeGoerli]: ['0', '0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6'], - // [chainIdTypeSepolia]: ['0', '0xeD43f81C17976372Fcb5786Dd214572e7dbB92c7'], - // [chainIdTypeOptimism]: ['0', '0x4200000000000000000000000000000000000006'], - [chainIdTypePolygon]: ['0x9c3C9283D3e44854697Cd22D3Faa240Cfb032889'], - // [chainIdTypeMoonbase]: ['0xD909178CC99d318e4D46e7E66a972955859670E1'], - // [chainIdTypeArbitrum]: ['0', '0xe39Ab88f8A4777030A534146A9Ca3B52bd5D43A3'], - // [chainIdTypeHermes]: ['0'], - // [chainIdTypeAthena]: ['0'], - // [chainIdTypeDemeter]: ['0'] - }; - - const endPoints: Record = { - // [chainIdTypeGoerli]: goerliEndPoints, - // [chainIdTypeSepolia]: sepoliaEndPoints, - // [chainIdTypeOptimism]: optimismEndPoints, - [chainIdTypePolygon]: polygonEndPoints, - // [chainIdTypeMoonbase]: moonbaseEndPoints, - }; - - const chainEndPoints: Record = { - // [chainIdTypeGoerli]: goerliEndPoints, - // [chainIdTypeSepolia]: sepoliaEndPoints, - // [chainIdTypeOptimism]: optimismEndPoints, - [chainIdTypePolygon]: polygonEndPoints, - // [chainIdTypeMoonbase]: moonbaseEndPoints, - }; - - const vbridge = await deploySignatureVBridge(tokens, deployers); - - // print out all the info for the addresses - const bridgeConfig = await vbridge.exportConfig(); - - const anchorIterable = bridgeConfig.vAnchors.values(); - const bridgeSideIterable = bridgeConfig.vBridgeSides.values(); - - for (const [anchor, bridgeSide] of zip(anchorIterable, bridgeSideIterable)) { - const chainId = await anchor.signer.getChainId(); - const anchorContractConfig: ContractConfig = { - address: anchor.contract.address.toLowerCase(), - deployedAt: anchor.contract.deployTransaction.blockNumber, - }; - const bridgeContractConfig: ContractConfig = { - address: bridgeSide.contract.address.toLowerCase(), - deployedAt: bridgeSide.contract.deployTransaction.blockNumber, - }; - const proposalSigningBackend: ProposalSigningBackend = { type: 'DKGNode', node: 'tangle' }; - const linkedAnchors: EvmLinkedAnchor[] = []; - const typedChainId = getChainIdType(chainId); - const beneficiary = '0xf1fd5607b90f6c421db7313158baac170d4f053b'; // relayer wallet address - - // Add all other anchors to the list of linked anchors - for (const otherAnchor of Array.from(bridgeConfig.vAnchors.values())) { - if (otherAnchor !== anchor) { - linkedAnchors.push({ - chainId: (await otherAnchor.contract.getChainId()).toString(), - address: otherAnchor.contract.address.toLowerCase(), - type: 'Evm', - }); - } - } - - const endPointConfig = chainEndPoints[typedChainId]; - // construct chain configuration - const chainConfig = getEvmChainConfig( - chainId, - anchorContractConfig, - bridgeContractConfig, - deployers[typedChainId], - linkedAnchors, - proposalSigningBackend, - endPointConfig, - beneficiary - ); - - const configString = JSON.stringify(chainConfig, null, 2); - console.log(configString); - // convert config to kebab case and write to json file - const dirPath = `${__dirname}/relayer-config`; - writeEvmChainConfig(`${dirPath}/${endPointConfig.name}.toml`, chainConfig); - - console.log( - `Anchor ${anchorContractConfig.address} deployed at: ${anchorContractConfig.deployedAt} for chainId ${chainId}` - ); - - console.log( - `BridgeSide ${bridgeContractConfig.address} deployed at: ${bridgeContractConfig.deployedAt} for chain ${chainId}` - ); - } - - for (const webbToken of Array.from(bridgeConfig.webbTokenAddresses.entries())) { - console.log(`webbToken entry: ${webbToken[0]} + ${webbToken[1].toLowerCase()}`); - } -} - -run(); diff --git a/scripts/evm/deployments/create2/create2Bridge.ts b/scripts/evm/deployments/create2/create2Bridge.ts deleted file mode 100644 index 89f97eaed..000000000 --- a/scripts/evm/deployments/create2/create2Bridge.ts +++ /dev/null @@ -1,336 +0,0 @@ -import { AnchorHandler, PoseidonHasher, VAnchor, Verifier } from '@webb-tools/anchors'; -import { DeterministicDeployFactory__factory } from '@webb-tools/contracts'; -import { Deployer } from '@webb-tools/create2-utils'; -import { DeployerConfig, GovernorConfig } from '@webb-tools/interfaces'; -import { - FungibleTokenWrapper, - TokenWrapperHandler, - Treasury, - TreasuryHandler, -} from '@webb-tools/tokens'; -import { ZkComponents, getChainIdType } from '@webb-tools/utils'; -import { SignatureBridgeSide } from '@webb-tools/vbridge'; -import { BaseContract, BigNumber, ethers } from 'ethers'; - -export type ExistingAssetInput = { - // A record of chainId => address of wrappable tokens to be supported in the webbToken. - asset: Record; -}; - -// Users define an input for a completely new bridge -export type VBridgeInput = { - // The tokens which should be supported after deploying from this bridge input - vAnchorInputs: ExistingAssetInput; - - // The IDs of the chains to deploy to - chainIDs: number[]; - - // The number of max edges for vanchors, if not provided, maxEdges is derived from passed chainIDs. - maxEdges?: number; - - // Existing webb tokens can be connected - webbTokens: Map; -}; - -export type BridgeConfig = { - // The addresses of tokens available to be transferred over this bridge config - // chainId => FungibleTokenWrapperAddress - webbTokenAddresses: Map; - - // The addresses of the anchors for the FungibleTokenWrapper - // {anchorIdentifier} => anchorAddress - vAnchors: Map; - - // The addresses of the Bridge contracts (bridgeSides) to interact with - vBridgeSides: Map>; -}; - -const zeroAddress = '0x0000000000000000000000000000000000000000'; - -function checkNativeAddress(tokenAddress: string): boolean { - if (tokenAddress === zeroAddress || tokenAddress === '0') { - return true; - } - return false; -} - -// A bridge is -export class Create2VBridge { - private constructor( - // Mapping of chainId => vBridgeSide - public vBridgeSides: Map>, - - // chainID => FungibleTokenWrapper (webbToken) address - public webbTokenAddresses: Map, - - // Mapping of resourceID => linkedVAnchor[]; so we know which - // vanchors need updating when the anchor for resourceID changes state. - public linkedVAnchors: Map, - - // Mapping of anchorIdString => Anchor for easy anchor access - public vAnchors: Map - ) {} - - //might need some editing depending on whether anchor identifier structure changes - public static createVAnchorIdString(chainId: number): string { - return chainId.toString(); - } - - // Takes as input a 2D array [[anchors to link together], [...]] - // And returns a map of resourceID => linkedAnchor[] - public static async createLinkedVAnchorMap( - createdVAnchors: VAnchor[][] - ): Promise> { - let linkedVAnchorMap = new Map(); - for (let groupedVAnchors of createdVAnchors) { - for (let i = 0; i < groupedVAnchors.length; i++) { - // create the resourceID of this anchor - let resourceID = await groupedVAnchors[i].createResourceId(); - let linkedVAnchors = []; - for (let j = 0; j < groupedVAnchors.length; j++) { - if (i != j) { - linkedVAnchors.push(groupedVAnchors[j]); - } - } - // insert the linked anchors into the linked map - linkedVAnchorMap.set(resourceID, linkedVAnchors); - } - } - return linkedVAnchorMap; - } - - // Deployments of all contracts for the bridge will be done with the DeployerConfig. - // After deployments, the wallet in the DeployerConfig will transfer ownership - // to the initialGovernor - public static async deployVariableAnchorBridge( - vBridgeInput: VBridgeInput, - deployers: DeployerConfig, - initialGovernors: GovernorConfig, - smallCircuitZkComponents: ZkComponents, - largeCircuitZkComponents: ZkComponents - ): Promise { - const salt = '999'; - const saltHex = ethers.utils.id(salt); - let webbTokenAddresses: Map = new Map(); - let vBridgeSides: Map> = new Map(); - let vAnchors: Map = new Map(); - // createdAnchors have the form of [[Anchors created on chainID], [...]] - // and anchors in the subArrays of thhe same index should be linked together - let createdVAnchors: VAnchor[][] = []; - - // Determine the maxEdges for the anchors on this VBridge deployment - let maxEdges = vBridgeInput.maxEdges ?? vBridgeInput.chainIDs.length > 2 ? 7 : 1; - - for (let chainID of vBridgeInput.chainIDs) { - const initialGovernor = initialGovernors[chainID]; - // Create the bridgeSide - let deployer: Deployer; - const Deployer1 = new DeterministicDeployFactory__factory(deployers[chainID]); - let deployer1Contract = await Deployer1.deploy(); - await deployer1Contract.deployed(); - deployer = new Deployer(deployer1Contract); - console.log('deployer address : ', deployer.address); - let vBridgeInstance = await SignatureBridgeSide.create2BridgeSide( - deployer, - saltHex, - deployers[chainID] - ); - const handler = await AnchorHandler.create2AnchorHandler( - vBridgeInstance.contract.address, - [], - [], - deployer, - saltHex, - deployers[chainID] - ); - vBridgeInstance.setAnchorHandler(handler); - // Create Treasury and TreasuryHandler - const treasuryHandler = await TreasuryHandler.create2TreasuryHandler( - vBridgeInstance.contract.address, - [], - [], - deployer, - saltHex, - vBridgeInstance.admin - ); - const treasury = await Treasury.create2Treasury( - treasuryHandler.contract.address, - deployer, - saltHex, - vBridgeInstance.admin - ); - await vBridgeInstance.setTreasuryHandler(treasuryHandler); - await vBridgeInstance.setTreasuryResourceWithSignature(treasury); - // Create the Hasher and Verifier for the chain - const hasherInstance = await PoseidonHasher.create2PoseidonHasher( - deployer, - saltHex, - deployers[chainID] - ); - const verifier = await Verifier.create2Verifier(deployer, saltHex, deployers[chainID]); - let verifierInstance = verifier.contract; - // Check the addresses of the asset. If it is zero, deploy a native token wrapper - let allowedNative: boolean = false; - for (const tokenToBeWrapped of vBridgeInput.vAnchorInputs.asset[chainID]!) { - // If passed '0' or zero address, token to be wrapped should support native. - if (checkNativeAddress(tokenToBeWrapped)) { - allowedNative = true; - } - } - - // Deploy TokenWrapperHandler - const tokenWrapperHandler = await TokenWrapperHandler.create2TokenWrapperHandler( - vBridgeInstance.contract.address, - [], - [], - deployer, - saltHex, - vBridgeInstance.admin - ); - let tokenInstance: FungibleTokenWrapper; - if (!vBridgeInput.webbTokens.get(chainID)) { - tokenInstance = await FungibleTokenWrapper.create2FungibleTokenWrapper( - `webbWETH`, - `webbWETH`, - 0, - treasury.contract.address, - tokenWrapperHandler.contract.address, - '10000000000000000000000000', - allowedNative, - deployer, - saltHex, - deployers[chainID] - ); - } else { - tokenInstance = vBridgeInput.webbTokens.get(chainID)!; - } - await vBridgeInstance.setTokenWrapperHandler(tokenWrapperHandler); - await vBridgeInstance.setFungibleTokenResourceWithSignature(tokenInstance); - // Add all token addresses to the governed token instance. - for (const tokenToBeWrapped of vBridgeInput.vAnchorInputs.asset[chainID]!) { - // if the address is not '0', then add it - if (!checkNativeAddress(tokenToBeWrapped)) { - await vBridgeInstance.executeAddTokenProposalWithSig(tokenInstance, tokenToBeWrapped); - } - } - // append each token - webbTokenAddresses.set(chainID, tokenInstance.contract.address); - - let chainGroupedVAnchors: VAnchor[] = []; - - // loop through all the anchor sizes on the token - const vAnchorInstance = await VAnchor.create2VAnchor( - deployer, - saltHex, - verifierInstance.address, - 30, - hasherInstance.contract.address, - handler.contract.address, - tokenInstance.contract.address, - maxEdges, - smallCircuitZkComponents, - largeCircuitZkComponents, - deployers[chainID] - ); - - // initialize vanchor contract instance - let tokenDenomination = '1000000000000000000'; // 1 ether - await vAnchorInstance.contract.initialize( - BigNumber.from(0).toString(), //minimum withdrawal limit - BigNumber.from(tokenDenomination).mul(1_000_000).toString() // max deposit limit - ); - - // grant minting rights to the anchor - await tokenInstance.grantMinterRole(vAnchorInstance.contract.address); - chainGroupedVAnchors.push(vAnchorInstance); - vAnchors.set(Create2VBridge.createVAnchorIdString(chainID), vAnchorInstance); - - // connect bridge, anchor and anchor handler set permissions - await vBridgeInstance.connectAnchorWithSignature(vAnchorInstance); - createdVAnchors.push(chainGroupedVAnchors); - - const governorAddress = - typeof initialGovernor === 'string' ? initialGovernor : initialGovernor.address; - const governorNonce = typeof initialGovernor === 'string' ? 0 : initialGovernor.nonce; - - // Transfer ownership of the bridge to the initialGovernor - const tx = await vBridgeInstance.transferOwnership(governorAddress, governorNonce); - await tx.wait(); - vBridgeSides.set(chainID, vBridgeInstance); - } - - // All anchors created, massage data to group anchors which should be linked together - let groupLinkedVAnchors: VAnchor[][] = []; - - // all subarrays will have the same number of elements - for (let i = 0; i < createdVAnchors[0].length; i++) { - let linkedAnchors: VAnchor[] = []; - for (let j = 0; j < createdVAnchors.length; j++) { - linkedAnchors.push(createdVAnchors[j][i]); - } - groupLinkedVAnchors.push(linkedAnchors); - } - - // link the anchors - const linkedVAnchorMap = await Create2VBridge.createLinkedVAnchorMap(groupLinkedVAnchors); - - return new Create2VBridge(vBridgeSides, webbTokenAddresses, linkedVAnchorMap, vAnchors); - } - - /** - * Updates the state of the BridgeSides and Anchors with - * the new state of the @param srcAnchor. - * @param srcAnchor The anchor that has updated. - * @returns - */ - public async updateLinkedVAnchors(srcAnchor: VAnchor) { - // Find the bridge sides that are connected to this Anchor - const linkedResourceID = await srcAnchor.createResourceId(); - const vAnchorsToUpdate = this.linkedVAnchors.get(linkedResourceID); - if (!vAnchorsToUpdate) { - return; - } - - // update the sides - for (let vAnchor of vAnchorsToUpdate) { - // get the bridge side which corresponds to this anchor - const chainId = getChainIdType(await vAnchor.signer.getChainId()); - const resourceID = await vAnchor.createResourceId(); - const vBridgeSide = this.vBridgeSides.get(chainId); - await vBridgeSide!.executeAnchorProposalWithSig(srcAnchor, resourceID); - } - } - - public async update(chainId: number) { - const vAnchor = this.getVAnchor(chainId); - if (!vAnchor) { - return; - } - await this.updateLinkedVAnchors(vAnchor); - } - - public getVBridgeSide(chainId: number) { - return this.vBridgeSides.get(chainId); - } - - public getVAnchor(chainId: number) { - let intendedAnchor: VAnchor = undefined; - intendedAnchor = this.vAnchors.get(Create2VBridge.createVAnchorIdString(chainId)); - return intendedAnchor; - } - - // Returns the address of the webbToken which wraps the given token name. - public getWebbTokenAddress(chainId: number): string | undefined { - return this.webbTokenAddresses.get(chainId); - } - - public exportConfig(): BridgeConfig { - return { - webbTokenAddresses: this.webbTokenAddresses, - vAnchors: this.vAnchors, - vBridgeSides: this.vBridgeSides, - }; - } -} - -export default Create2VBridge; diff --git a/scripts/evm/deployments/create2/deploymentScript.ts b/scripts/evm/deployments/create2/deploymentScript.ts deleted file mode 100644 index e04c464ab..000000000 --- a/scripts/evm/deployments/create2/deploymentScript.ts +++ /dev/null @@ -1,171 +0,0 @@ -import { FungibleTokenWrapper } from '@webb-tools/tokens'; -import { fetchComponentsFromFilePaths, getChainIdType, vanchorFixtures } from '@webb-tools/utils'; -import { DeployerConfig } from '@webb-tools/interfaces'; -import path from 'path'; -import { ethers } from 'ethers'; -import { - chainIdTypeGoerli, - chainIdTypeOptimism, - chainIdTypeArbitrum, - chainIdTypePolygon, - chainIdTypeMoonbase, - walletGoerli, - walletOptimism, - walletArbitrum, - walletPolygon, - walletMoonbase, - chainIdTypeHermes, - chainIdTypeAthena, - chainIdTypeDemeter, - walletHermes, - walletAthena, - walletDemeter, - chainIdTypeSepolia, - walletSepolia, - chainIdTypeAurora, -} from '../../ethersGovernorWallets'; -import { EvmLinkedAnchor, ProposalSigningBackend } from '@webb-tools/test-utils'; -import { ContractConfig, getEvmChainConfig, writeEvmChainConfig } from '../utils'; -import { zip } from 'itertools'; -import { - EndPointConfig, - goerliEndPoints, - moonbaseEndPoints, - optimismEndPoints, - polygonEndPoints, - sepoliaEndPoints, -} from '../endPoints'; -import Create2VBridge from './create2Bridge'; - -async function deploySignatureVBridge( - tokens: Record, - deployers: DeployerConfig -): Promise { - let assetRecord: Record = {}; - let chainIdsArray: number[] = []; - let existingWebbTokens = new Map(); - let governorConfig: Record = {}; - - for (const chainIdType of Object.keys(deployers)) { - assetRecord[chainIdType] = tokens[chainIdType]; - chainIdsArray.push(Number(chainIdType)); - governorConfig[Number(chainIdType)] = await deployers[chainIdType].getAddress(); - existingWebbTokens[chainIdType] = null; - console.log(tokens[chainIdType]); - } - - const bridgeInput = { - vAnchorInputs: { - asset: assetRecord, - }, - chainIDs: chainIdsArray, - webbTokens: existingWebbTokens, - }; - - console.log(bridgeInput); - - const zkComponentsSmall = await vanchorFixtures[28](); - const zkComponentsLarge = await vanchorFixtures[168](); - - console.log(governorConfig); - - return Create2VBridge.deployVariableAnchorBridge( - bridgeInput, - deployers, - governorConfig, - zkComponentsSmall, - zkComponentsLarge - ); -} - -async function run() { - const deployers: DeployerConfig = { - // [chainIdTypeGoerli]: walletGoerli, - [chainIdTypeSepolia]: walletSepolia, - // [chainIdTypeOptimism]: walletOptimism, - // [chainIdTypePolygon]: walletPolygon, - // [chainIdTypeMoonbase]: walletMoonbase, - }; - - const tokens: Record = { - // [chainIdTypeGoerli]: ["0", ""], - [chainIdTypeSepolia]: ['0', '0xeD43f81C17976372Fcb5786Dd214572e7dbB92c7'], - // [chainIdTypeOptimism]: ["0", "0x4200000000000000000000000000000000000006"], - // [chainIdTypePolygon]: ["0x9c3C9283D3e44854697Cd22D3Faa240Cfb032889"], - // [chainIdTypeMoonbase]: ["0xD909178CC99d318e4D46e7E66a972955859670E1"], - }; - - const endPoints: Record = { - // [chainIdTypeGoerli]: goerliEndPoints, - [chainIdTypeSepolia]: sepoliaEndPoints, - // [chainIdTypeOptimism]: optimismEndPoints, - // [chainIdTypePolygon]: polygonEndPoints, - // [chainIdTypeMoonbase]: moonbaseEndPoints, - }; - - const vbridge = await deploySignatureVBridge(tokens, deployers); - - // print out all the info for the addresses - const bridgeConfig = await vbridge.exportConfig(); - - const anchorIterable = bridgeConfig.vAnchors.values(); - const bridgeSideIterable = bridgeConfig.vBridgeSides.values(); - - for (const [anchor, bridgeSide] of zip(anchorIterable, bridgeSideIterable)) { - const chainId = await anchor.signer.getChainId(); - const anchorContractConfig: ContractConfig = { - address: anchor.contract.address.toLowerCase(), - deployedAt: anchor.contract.deployTransaction.blockNumber ?? 1, - }; - const bridgeContractConfig: ContractConfig = { - address: bridgeSide.contract.address.toLowerCase(), - deployedAt: bridgeSide.contract.deployTransaction.blockNumber ?? 1, - }; - const proposalSigningBackend: ProposalSigningBackend = { type: 'DKGNode', node: 'tangle' }; - const linkedAnchors: EvmLinkedAnchor[] = []; - const typedChainId = getChainIdType(chainId); - const beneficiary = '0xf1fd5607b90f6c421db7313158baac170d4f053b'; // relayer wallet address - - // Add all other anchors to the list of linked anchors - for (const otherAnchor of Array.from(bridgeConfig.vAnchors.values())) { - if (otherAnchor !== anchor) { - linkedAnchors.push({ - chainId: (await otherAnchor.contract.getChainId()).toString(), - address: otherAnchor.contract.address.toLowerCase(), - type: 'Evm', - }); - } - } - - const endPointConfig = endPoints[typedChainId]; - // construct chain configuration - const chainConfig = getEvmChainConfig( - chainId, - anchorContractConfig, - bridgeContractConfig, - deployers[typedChainId], - linkedAnchors, - proposalSigningBackend, - endPointConfig, - beneficiary - ); - - // convert config to kebab case and write to json file - const dirPath = `${__dirname}/relayer-config`; - writeEvmChainConfig(`${dirPath}/${endPointConfig.name}.toml`, chainConfig); - - console.log( - `Anchor ${anchorContractConfig.address} deployed at: ${anchorContractConfig.deployedAt} for chainId ${chainId}` - ); - - console.log( - `BridgeSide ${bridgeContractConfig.address} deployed at: ${bridgeContractConfig.deployedAt} for chain ${chainId}` - ); - } - - for (const webbToken of Array.from(bridgeConfig.webbTokenAddresses.entries())) { - console.log(`webbToken entry: ${webbToken[0]} + ${webbToken[1].toLowerCase()}`); - } -} - -run(); diff --git a/scripts/evm/deployments/create2/relayer-config/goerli.toml b/scripts/evm/deployments/create2/relayer-config/goerli.toml deleted file mode 100644 index e6047ce98..000000000 --- a/scripts/evm/deployments/create2/relayer-config/goerli.toml +++ /dev/null @@ -1,44 +0,0 @@ -[evm] - -[evm.goerli] -beneficiary = "0xf1fd5607b90f6c421db7313158baac170d4f053b" -block-confirmations = 0 -chain-id = 5 -enabled = true -http-endpoint = "https://goerli.infura.io/v3/${INFURA_PROJECT_ID}" -name = "goerli" -private-key = "$GOERLI_PRIVATE_KEY" -ws-endpoint = "wss://goerli.infura.io/ws/v3/${INFURA_PROJECT_ID}" - -[[evm.goerli.contracts]] -address = "0x77f0a3aca922339dc91b82e88a83231ef2dacf02" -contract = "VAnchor" -deployed-at = 8363946 -linked-anchors = [ - { type = "Evm", chain = "sepolia", chain-id = 11155111, address = "0x77f0a3aca922339dc91b82e88a83231ef2dacf02" }, - { type = "Evm", chain = "optimism", chain-id = 420, address = "0x77f0a3aca922339dc91b82e88a83231ef2dacf02" }, - { type = "Evm", chain = "moonbase", chain-id = 1287, address = "0x77f0a3aca922339dc91b82e88a83231ef2dacf02"} -] - -[evm.goerli.contracts.events-watcher] -enabled = true -polling-interval = 1000 -print-progress-interval = 7000 - -[evm.goerli.contracts.proposal-signing-backend] -node = "tangle" -type = "DKGNode" - -[evm.goerli.contracts.withdraw-config] -withdraw-fee-percentage = 0 -withdraw-gaslimit = "0x5B8D80" - -[[evm.goerli.contracts]] -address = "0x8a568614cf78ae79ab8d326fd1d3fd5bf888aae7" -contract = "SignatureBridge" -deployed-at = 8363923 - -[evm.goerli.contracts.events-watcher] -enabled = true -polling-interval = 1000 -print-progress-interval = 7000 \ No newline at end of file diff --git a/scripts/evm/deployments/create2/relayer-config/moonbase.toml b/scripts/evm/deployments/create2/relayer-config/moonbase.toml deleted file mode 100644 index 30ae62e74..000000000 --- a/scripts/evm/deployments/create2/relayer-config/moonbase.toml +++ /dev/null @@ -1,44 +0,0 @@ -[evm] - -[evm.moonbase] -beneficiary = "0xf1fd5607b90f6c421db7313158baac170d4f053b" -block-confirmations = 0 -chain-id = 1287 -enabled = true -http-endpoint = "https://moonbeam.public.blastapi.io" -name = "moonbase" -private-key = "$MOONBASE_PRIVATE_KEY" -ws-endpoint = "wss://wss.api.moonbeam.network" - -[[evm.moonbase.contracts]] -address = "0x77f0a3aca922339dc91b82e88a83231ef2dacf02" -contract = "VAnchor" -deployed-at = 3604703 -linked-anchors = [ - { type = "Evm", chain = "goerli", chain-id = 5, address = "0x77f0a3aca922339dc91b82e88a83231ef2dacf02" }, - { type = "Evm", chain = "sepolia", chain-id = 11155111, address = "0x77f0a3aca922339dc91b82e88a83231ef2dacf02" }, - { type = "Evm", chain = "optimism", chain-id = 420, address = "0x77f0a3aca922339dc91b82e88a83231ef2dacf02" }, -] - -[evm.moonbase.contracts.events-watcher] -enabled = true -polling-interval = 1000 -print-progress-interval = 7000 - -[evm.moonbase.contracts.proposal-signing-backend] -node = "tangle" -type = "DKGNode" - -[evm.moonbase.contracts.withdraw-config] -withdraw-fee-percentage = 0 -withdraw-gaslimit = "0x5B8D80" - -[[evm.moonbase.contracts]] -address = "0x8a568614cf78ae79ab8d326fd1d3fd5bf888aae7" -contract = "SignatureBridge" -deployed-at = 3604670 - -[evm.moonbase.contracts.events-watcher] -enabled = true -polling-interval = 1000 -print-progress-interval = 7000 \ No newline at end of file diff --git a/scripts/evm/deployments/create2/relayer-config/optimism.toml b/scripts/evm/deployments/create2/relayer-config/optimism.toml deleted file mode 100644 index fc22b07ba..000000000 --- a/scripts/evm/deployments/create2/relayer-config/optimism.toml +++ /dev/null @@ -1,44 +0,0 @@ -[evm] - -[evm.optimism] -beneficiary = "0xf1fd5607b90f6c421db7313158baac170d4f053b" -block-confirmations = 0 -chain-id = 420 -enabled = true -http-endpoint = "https://goerli.optimism.io" -name = "optimism" -private-key = "$OPTIMISM_TESTNET_PRIVATE_KEY" -ws-endpoint = "wss://goerli.optimism.io" - -[[evm.optimism.contracts]] -address = "0x77f0a3aca922339dc91b82e88a83231ef2dacf02" -contract = "VAnchor" -deployed-at = 4538203 -linked-anchors = [ - { type = "Evm", chain = "goerli", chain-id = 5, address = "0x77f0a3aca922339dc91b82e88a83231ef2dacf02" }, - { type = "Evm", chain = "sepolia", chain-id = 11155111, address = "0x77f0a3aca922339dc91b82e88a83231ef2dacf02" }, - { type = "Evm", chain = "moonbase", chain-id = 1287, address = "0x77f0a3aca922339dc91b82e88a83231ef2dacf02"} -] - -[evm.optimism.contracts.events-watcher] -enabled = true -polling-interval = 1000 -print-progress-interval = 7000 - -[evm.optimism.contracts.proposal-signing-backend] -node = "tangle" -type = "DKGNode" - -[evm.optimism.contracts.withdraw-config] -withdraw-fee-percentage = 0 -withdraw-gaslimit = "0x5B8D80" - -[[evm.optimism.contracts]] -address = "0x8a568614cf78ae79ab8d326fd1d3fd5bf888aae7" -contract = "SignatureBridge" -deployed-at = 4538067 - -[evm.optimism.contracts.events-watcher] -enabled = true -polling-interval = 1000 -print-progress-interval = 7000 \ No newline at end of file diff --git a/scripts/evm/deployments/create2/relayer-config/sepolia.toml b/scripts/evm/deployments/create2/relayer-config/sepolia.toml deleted file mode 100644 index e1026ded6..000000000 --- a/scripts/evm/deployments/create2/relayer-config/sepolia.toml +++ /dev/null @@ -1,44 +0,0 @@ -[evm] - -[evm.sepolia] -beneficiary = "0xf1fd5607b90f6c421db7313158baac170d4f053b" -block-confirmations = 0 -chain-id = 11155111 -enabled = true -http-endpoint = "https://rpc.sepolia.org" -name = "sepolia" -private-key = "$SEPOLIA_PRIVATE_KEY" -ws-endpoint = "wss://rpc.sepolia.dev" - -[[evm.sepolia.contracts]] -address = "0x77f0a3aca922339dc91b82e88a83231ef2dacf02" -contract = "VAnchor" -deployed-at = 2750880 -linked-anchors = [ - { type = "Evm", chain = "goerli", chain-id = 5, address = "0x77f0a3aca922339dc91b82e88a83231ef2dacf02" }, - { type = "Evm", chain = "optimism", chain-id = 420, address = "0x77f0a3aca922339dc91b82e88a83231ef2dacf02" }, - { type = "Evm", chain = "moonbase", chain-id = 1287, address = "0x77f0a3aca922339dc91b82e88a83231ef2dacf02"} -] - -[evm.sepolia.contracts.events-watcher] -enabled = true -polling-interval = 1000 -print-progress-interval = 7000 - -[evm.sepolia.contracts.proposal-signing-backend] -node = "tangle" -type = "DKGNode" - -[evm.sepolia.contracts.withdraw-config] -withdraw-fee-percentage = 0 -withdraw-gaslimit = "0x5B8D80" - -[[evm.sepolia.contracts]] -address = "0x8a568614cf78ae79ab8d326fd1d3fd5bf888aae7" -contract = "SignatureBridge" -deployed-at = 2750859 - -[evm.sepolia.contracts.events-watcher] -enabled = true -polling-interval = 1000 -print-progress-interval = 7000 \ No newline at end of file diff --git a/scripts/evm/deployments/endPoints.ts b/scripts/evm/deployments/endPoints.ts deleted file mode 100644 index 62a264e4f..000000000 --- a/scripts/evm/deployments/endPoints.ts +++ /dev/null @@ -1,35 +0,0 @@ -export type EndPointConfig = { - httpEndpoint: string; - wsEndpoint: string; - name: string; -}; - -export const polygonEndPoints: EndPointConfig = { - httpEndpoint: process.env.MUMBAI_TESTNET_HTTPS_URL!, - wsEndpoint: process.env.MUMBAI_TESTNET_WSS_URL!, - name: 'mumbai', -}; - -export const sepoliaEndPoints: EndPointConfig = { - httpEndpoint: process.env.SEPOLIA_HTTPS_URL!, - wsEndpoint: process.env.SEPOLIA_WSS_URL!, - name: 'sepolia', -}; - -export const optimismEndPoints: EndPointConfig = { - httpEndpoint: process.env.OPTIMISM_TESTNET_HTTPS_URL!, - wsEndpoint: process.env.OPTIMISM_TESTNET_WSS_URL!, - name: 'optimism', -}; - -export const moonbaseEndPoints: EndPointConfig = { - httpEndpoint: process.env.MOONBASE_HTTPS_URL!, - wsEndpoint: process.env.MOONBASE_WSS_URL!, - name: 'moonbase', -}; - -export const goerliEndPoints: EndPointConfig = { - httpEndpoint: process.env.GOERLI_HTTPS_URL!, - wsEndpoint: process.env.GOERLI_WSS_URL!, - name: 'goerli', -}; diff --git a/scripts/evm/deployments/relayer-config/moonbase.toml b/scripts/evm/deployments/relayer-config/moonbase.toml deleted file mode 100644 index cb2c9865d..000000000 --- a/scripts/evm/deployments/relayer-config/moonbase.toml +++ /dev/null @@ -1,40 +0,0 @@ -[evm] - -[evm.moonbase] -beneficiary = "0xf1fd5607b90f6c421db7313158baac170d4f053b" -block-confirmations = 0.0 -chain-id = 1287.0 -enabled = true -name = "moonbase" -private-key = "0xc086596a698603ae7428a61ced569cbc5fdc24ae91c341d692aad590febc4429" - -[[evm.moonbase.contracts]] -address = "0x57f608d6561f0e354026f17f492c57311fd8080f" -contract = "VAnchor" - -[evm.moonbase.contracts.events-watcher] -enabled = true -polling-interval = 1000.0 -print-progress-interval = 7000.0 - -[[evm.moonbase.contracts.linked-anchors]] -address = "0x2552f59ff85602bff8043ca6ef94b1cbb14dc2c6" -chain-id = "80001" -type = "Evm" - -[evm.moonbase.contracts.proposal-signing-backend] -node = "tangle" -type = "DKGNode" - -[evm.moonbase.contracts.withdraw-config] -withdraw-fee-percentage = 0.0 -withdraw-gaslimit = "0x5B8D80" - -[[evm.moonbase.contracts]] -address = "0x12cec77a5ac7108ae453a7aeb6a31afc2b6f0bd8" -contract = "SignatureBridge" - -[evm.moonbase.contracts.events-watcher] -enabled = true -polling-interval = 1000.0 -print-progress-interval = 7000.0 \ No newline at end of file diff --git a/scripts/evm/deployments/relayer-config/mumbai.toml b/scripts/evm/deployments/relayer-config/mumbai.toml deleted file mode 100644 index 59bfd75ff..000000000 --- a/scripts/evm/deployments/relayer-config/mumbai.toml +++ /dev/null @@ -1,40 +0,0 @@ -[evm] - -[evm.mumbai] -beneficiary = "0xf1fd5607b90f6c421db7313158baac170d4f053b" -block-confirmations = 0.0 -chain-id = 80001.0 -enabled = true -name = "mumbai" -private-key = "0xc086596a698603ae7428a61ced569cbc5fdc24ae91c341d692aad590febc4429" - -[[evm.mumbai.contracts]] -address = "0x2552f59ff85602bff8043ca6ef94b1cbb14dc2c6" -contract = "VAnchor" - -[evm.mumbai.contracts.events-watcher] -enabled = true -polling-interval = 1000.0 -print-progress-interval = 7000.0 - -[[evm.mumbai.contracts.linked-anchors]] -address = "0x57f608d6561f0e354026f17f492c57311fd8080f" -chain-id = "1287" -type = "Evm" - -[evm.mumbai.contracts.proposal-signing-backend] -node = "tangle" -type = "DKGNode" - -[evm.mumbai.contracts.withdraw-config] -withdraw-fee-percentage = 0.0 -withdraw-gaslimit = "0x5B8D80" - -[[evm.mumbai.contracts]] -address = "0xca216c5dee21f69ff8ebaa32b26b0165bcd7d9c0" -contract = "SignatureBridge" - -[evm.mumbai.contracts.events-watcher] -enabled = true -polling-interval = 1000.0 -print-progress-interval = 7000.0 \ No newline at end of file diff --git a/scripts/evm/deployments/utils.ts b/scripts/evm/deployments/utils.ts deleted file mode 100644 index bd904295b..000000000 --- a/scripts/evm/deployments/utils.ts +++ /dev/null @@ -1,168 +0,0 @@ -import fs from 'fs'; -import { - Contract, - ConvertToKebabCase, - EventsWatcher, - FullChainInfo, - LinkedAnchor, - ProposalSigningBackend, - WithdrawConfig, -} from '@webb-tools/test-utils'; -import { EndPointConfig } from './endPoints'; -import { Wallet } from 'ethers'; -import { toToml } from 'tomlify-j0.4'; - -// Default WithdrawlConfig for the contracts. -const defaultWithdrawConfigValue: WithdrawConfig = { - withdrawGaslimit: '0x5B8D80', - withdrawFeePercentage: 0, -}; - -// Default Event watcher config. -const defaultEventWatcherConfigValue: EventsWatcher = { - enabled: true, - pollingInterval: 1000, - printProgressInterval: 7000, -}; - -export type ContractConfig = { - address: string; - deployedAt: number; -}; - -export function getEvmChainConfig( - chainId: number, - anchor: ContractConfig, - bridge: ContractConfig, - deployerWallet: Wallet, - linkedAnchors: LinkedAnchor[], - proposalSigningBackend: ProposalSigningBackend, - endpoint: EndPointConfig, - beneficiary?: string -): FullChainInfo { - const contracts: Contract[] = [ - // first the local Anchor - { - contract: 'VAnchor', - address: anchor.address, - deployedAt: anchor.deployedAt, - size: 1, // Ethers - proposalSigningBackend: proposalSigningBackend, - withdrawConfig: defaultWithdrawConfigValue, - eventsWatcher: defaultEventWatcherConfigValue, - linkedAnchors: linkedAnchors, - }, - { - contract: 'SignatureBridge', - address: bridge.address, - deployedAt: bridge.deployedAt, - eventsWatcher: defaultEventWatcherConfigValue, - }, - ]; - const chainInfo: FullChainInfo = { - name: endpoint.name, - enabled: true, - httpEndpoint: endpoint.httpEndpoint, - wsEndpoint: endpoint.wsEndpoint, - blockConfirmations: 0, - chainId: chainId, - beneficiary: beneficiary ?? '', - privateKey: deployerWallet.privateKey, - contracts: contracts, - }; - return chainInfo; -} - -export function writeEvmChainConfig(path: string, config: FullChainInfo) { - type ConvertedLinkedAnchor = ConvertToKebabCase; - type ConvertedContract = Omit< - ConvertToKebabCase, - | 'events-watcher' - | 'proposal-signing-backend' - | 'withdraw-config' - | 'linked-anchors' - | 'deployed-at' - > & { - 'events-watcher': ConvertToKebabCase; - 'proposal-signing-backend'?: ConvertToKebabCase; - 'withdraw-config'?: ConvertToKebabCase; - 'linked-anchors'?: ConvertedLinkedAnchor[]; - }; - type ConvertedConfig = Omit, 'contracts'> & { - contracts: ConvertedContract[]; - }; - type FullConfigFile = { - evm: { - // chainId as the chain identifier - [key: number]: ConvertedConfig; - }; - }; - - const convertedConfig: ConvertedConfig = { - beneficiary: config.beneficiary, - 'block-confirmations': config.blockConfirmations, - 'chain-id': config.chainId, - contracts: config.contracts.map((contract) => ({ - address: contract.address, - contract: contract.contract, - 'deployed-at': contract.deployedAt, - 'events-watcher': { - enabled: contract.eventsWatcher.enabled, - 'polling-interval': contract.eventsWatcher.pollingInterval, - 'print-progress-interval': contract.eventsWatcher.printProgressInterval, - }, - 'linked-anchors': contract?.linkedAnchors?.map((anchor: LinkedAnchor) => - anchor.type === 'Evm' - ? { - address: anchor.address, - 'chain-id': anchor.chainId, - type: 'Evm', - } - : anchor.type === 'Substrate' - ? { - 'chain-id': anchor.chainId, - pallet: anchor.pallet, - 'tree-id': anchor.treeId, - type: 'Substrate', - } - : { - 'resource-id': anchor.resourceId, - type: 'Raw', - } - ), - 'proposal-signing-backend': - contract.proposalSigningBackend?.type === 'Mocked' - ? { - 'private-key': contract.proposalSigningBackend?.privateKey, - type: 'Mocked', - } - : contract.proposalSigningBackend?.type === 'DKGNode' - ? { - node: contract.proposalSigningBackend?.node, - type: 'DKGNode', - } - : undefined, - 'withdraw-config': contract.withdrawConfig - ? { - 'withdraw-fee-percentage': contract.withdrawConfig?.withdrawFeePercentage, - 'withdraw-gaslimit': contract.withdrawConfig?.withdrawGaslimit, - } - : undefined, - })), - enabled: config.enabled, - 'http-endpoint': config.httpEndpoint, - name: config.name, - 'private-key': config.privateKey, - 'ws-endpoint': config.wsEndpoint, - }; - const fullConfigFile: FullConfigFile = { - evm: { - [config.name]: convertedConfig, - }, - }; - - const toml = toToml(fullConfigFile, { spaces: 4 }); - - // Write the TOML string to a file - fs.writeFileSync(path, toml); -} diff --git a/scripts/evm/ethersGovernorWallets.ts b/scripts/evm/ethersGovernorWallets.ts deleted file mode 100644 index 8f25e0380..000000000 --- a/scripts/evm/ethersGovernorWallets.ts +++ /dev/null @@ -1,64 +0,0 @@ -require('dotenv').config(); -import { getChainIdType } from '@webb-tools/utils'; -import { ethers } from 'ethers'; - -export const providerPolygon = new ethers.providers.JsonRpcProvider(process.env.POLYGON_KEY!); -export const walletPolygon = new ethers.Wallet(process.env.PRIVATE_KEY!, providerPolygon); -export const chainIdTypePolygon = getChainIdType(80001); - -export const providerGoerli = new ethers.providers.JsonRpcProvider( - `https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161` -); -export const walletGoerli = new ethers.Wallet(process.env.PRIVATE_KEY!, providerGoerli); -export const chainIdTypeGoerli = getChainIdType(5); - -export const providerSepolia = new ethers.providers.JsonRpcProvider(`https://rpc.sepolia.org`); -export const walletSepolia = new ethers.Wallet(process.env.PRIVATE_KEY!, providerSepolia); -export const chainIdTypeSepolia = getChainIdType(11155111); - -export const providerOptimism = new ethers.providers.JsonRpcProvider(process.env.OPTIMISM_KEY!); -export const walletOptimism = new ethers.Wallet(process.env.PRIVATE_KEY!, providerOptimism); -export const chainIdTypeOptimism = getChainIdType(420); - -export const providerArbitrum = new ethers.providers.JsonRpcProvider(process.env.ARBITRUM_KEY!); -export const walletArbitrum = new ethers.Wallet(process.env.PRIVATE_KEY!, providerArbitrum); -export const chainIdTypeArbitrum = getChainIdType(421613); - -export const providerMoonbase = new ethers.providers.JsonRpcProvider( - 'https://moonbeam-alpha.api.onfinality.io/public' -); -export const walletMoonbase = new ethers.Wallet(process.env.PRIVATE_KEY!, providerMoonbase); -export const chainIdTypeMoonbase = getChainIdType(1287); - -export const providerAvalanche = new ethers.providers.JsonRpcProvider(process.env.AVALANCHE_KEY!); -export const walletAvalanche = new ethers.Wallet(process.env.PRIVATE_KEY!, providerAvalanche); -export const chainIdTypeAvalanche = getChainIdType(43113); - -export const providerAurora = new ethers.providers.JsonRpcProvider(process.env.AURORA_KEY!); -export const walletAurora = new ethers.Wallet(process.env.PRIVATE_KEY!, providerAurora); -export const chainIdTypeAurora = getChainIdType(1313161555); - -export const providerHarmony = new ethers.providers.JsonRpcProvider(process.env.HARMONY_KEY!); -export const walletHarmony = new ethers.Wallet(process.env.PRIVATE_KEY!, providerHarmony); -export const chainIdTypeHarmony = getChainIdType(1666700000); - -export const providerAthena = new ethers.providers.JsonRpcProvider(`http://127.0.0.1:5002`); -export const walletAthena = new ethers.Wallet( - '0x0000000000000000000000000000000000000000000000000000000000000001', - providerAthena -); -export const chainIdTypeAthena = getChainIdType(5002); - -export const providerHermes = new ethers.providers.JsonRpcProvider(`http://127.0.0.1:5001`); -export const walletHermes = new ethers.Wallet( - '0x0000000000000000000000000000000000000000000000000000000000000001', - providerHermes -); -export const chainIdTypeHermes = getChainIdType(5001); - -export const providerDemeter = new ethers.providers.JsonRpcProvider(`http://127.0.0.1:5003`); -export const walletDemeter = new ethers.Wallet( - '0x0000000000000000000000000000000000000000000000000000000000000001', - providerDemeter -); -export const chainIdTypeDemeter = getChainIdType(5003); diff --git a/scripts/evm/playground.ts b/scripts/evm/playground.ts deleted file mode 100644 index e4ecb5b3e..000000000 --- a/scripts/evm/playground.ts +++ /dev/null @@ -1,37 +0,0 @@ -require('dotenv').config(); -const path = require('path'); -import { BigNumber, ethers } from 'ethers'; -import { - toFixedHex, - Note, - Keypair, - CircomUtxo, - Utxo, - calculateTypedChainId, - ChainType, -} from '@webb-tools/sdk-core'; -import type { JsNote } from '@webb-tools/wasm-utils'; -import { VAnchor, VAnchor__factory } from '@webb-tools/contracts'; -import { hexToU8a, u8aToHex } from '@webb-tools/utils'; - -const providerGanache = new ethers.providers.JsonRpcProvider(`http://localhost:5001`); -const walletGanache = new ethers.Wallet(process.env.PRIVATE_KEY!, providerGanache); - -export const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); - -async function run() { - const vanchor = VAnchor__factory.connect( - '0x4e3df2073bf4b43b9944b8e5a463b1e185d6448c', - walletGanache - ); - - const utxo = await CircomUtxo.generateUtxo({ - amount: '1000000', - backend: 'Circom', - curve: 'Bn254', - chainId: calculateTypedChainId(ChainType.EVM, 5002).toString(), - originChainId: calculateTypedChainId(ChainType.EVM, 5001).toString(), - }); -} - -run(); diff --git a/scripts/evm/tokens/approveTokenSpend.ts b/scripts/evm/tokens/approveTokenSpend.ts deleted file mode 100644 index 4654be562..000000000 --- a/scripts/evm/tokens/approveTokenSpend.ts +++ /dev/null @@ -1,23 +0,0 @@ -// @ts-nocheck -import { ethers } from 'ethers'; -import { ERC20 } from '../../../typechain/ERC20'; -import { ERC20__factory } from '../../../typechain/factories/ERC20__factory'; -require('dotenv').config({ path: '../.env' }); - -export async function approveTokenSpend( - tokenAddress: string, - spenderAddress: string, - passedWallet: ethers.Signer -) { - const token: ERC20 = ERC20__factory.connect(tokenAddress, passedWallet); - let tx = await token.approve(spenderAddress, '1000000000000000000000000000', { - from: await passedWallet.getAddress(), - gasLimit: '0x5B8D80', - }); - await tx.wait(); - - const name = await token.name(); - console.log( - `The account: ${await passedWallet.getAddress()} approved ${spenderAddress} to spend ${name}` - ); -} diff --git a/scripts/evm/tokens/getTokenAllowance.ts b/scripts/evm/tokens/getTokenAllowance.ts deleted file mode 100644 index b34c2a82b..000000000 --- a/scripts/evm/tokens/getTokenAllowance.ts +++ /dev/null @@ -1,21 +0,0 @@ -// @ts-nocheck -import { ethers } from 'ethers'; -import { ERC20__factory } from '../../../typechain/factories/ERC20__factory'; -require('dotenv').config({ path: '../.env' }); - -export async function getTokenAllowance( - tokenAddress: string, - ownerAddress: string, - spenderAddress: string, - provider: ethers.providers.Provider -) { - const token = ERC20__factory.connect(tokenAddress, provider); - - const balance = await token.allowance(ownerAddress, spenderAddress); - const name = await token.name(); - console.log( - `The account: ${ownerAddress} has already given ${spenderAddress} permission to spend ${balance} for ${name}` - ); - - return balance; -} diff --git a/scripts/evm/tokens/getTokenBalance.ts b/scripts/evm/tokens/getTokenBalance.ts deleted file mode 100644 index e3abf963f..000000000 --- a/scripts/evm/tokens/getTokenBalance.ts +++ /dev/null @@ -1,18 +0,0 @@ -// @ts-nocheck -import { ethers } from 'ethers'; -import { ERC20__factory } from '../../../typechain/factories/ERC20__factory'; -require('dotenv').config(); - -export async function getTokenBalance( - tokenAddress: string, - checkAddress: string, - passedProvider: ethers.providers.Provider -) { - const token = ERC20__factory.connect(tokenAddress, passedProvider); - - const balance = await token.balanceOf(checkAddress); - const name = await token.name(); - console.log(`The account: ${checkAddress} has a balance of ${balance} for ${name}`); - - return balance; -} diff --git a/scripts/evm/tokens/viewTokensInWrapper.ts b/scripts/evm/tokens/viewTokensInWrapper.ts deleted file mode 100644 index 3023d5266..000000000 --- a/scripts/evm/tokens/viewTokensInWrapper.ts +++ /dev/null @@ -1,20 +0,0 @@ -// @ts-nocheck -require('dotenv').config({ path: '../.env' }); -import { ethers } from 'ethers'; -import { FungibleTokenWrapper__factory } from '../../../typechain/factories/FungibleTokenWrapper__factory'; - -export async function viewTokensInWrapper( - tokenWrapperAddress: string, - passedProvider: ethers.providers.JsonRpcProvider -) { - const fungibleTokenWrapper = FungibleTokenWrapper__factory.connect( - tokenWrapperAddress, - passedProvider - ); - const tokens = await fungibleTokenWrapper.functions.getTokens(); - - const allowedNative = await fungibleTokenWrapper.isNativeAllowed(); - console.log('Tokens in the wrapper: '); - console.log(tokens); - console.log('nativeAllowed? ', allowedNative); -} diff --git a/scripts/evm/viewActions/viewRootAcrossBridge.ts b/scripts/evm/viewActions/viewRootAcrossBridge.ts deleted file mode 100644 index 1e8a1f0bf..000000000 --- a/scripts/evm/viewActions/viewRootAcrossBridge.ts +++ /dev/null @@ -1,13 +0,0 @@ -require('dotenv').config(); -import { ethers } from 'ethers'; -import { VAnchor } from '@webb-tools/contracts'; - -export async function viewRootAcrossBridge(merkleRootAnchor: VAnchor, edgeListAnchor: VAnchor) { - const lastMerkleRoot = await merkleRootAnchor.getLastRoot(); - const merkleChainId = await merkleRootAnchor.signer.getChainId(); - - console.log('Latest merkle root on chain: ', lastMerkleRoot); - const merkleRootIndex = await edgeListAnchor.edgeIndex(merkleChainId); - const edgeListEntry = await edgeListAnchor.edgeList(merkleRootIndex); - console.log('edge list entry: ', edgeListEntry); -} diff --git a/tsconfig.build.json b/tsconfig.build.json index 559a58cc3..3ad61fb5a 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -1,7 +1,7 @@ { "exclude": [ "node_modules/**/*", - "**/node_modules/**/*" + "**/node_modules/**/*", ], "include": [ "packages/**/*", @@ -13,8 +13,9 @@ "esModuleInterop": true, "module": "commonjs", "moduleResolution": "node", - "target": "es5", + "target": "ES2017", "noEmitOnError": true, - "skipLibCheck": true + "skipLibCheck": true, + "preserveSymlinks": true } } \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index d378be504..91939f840 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,7 @@ { "extends": "./tsconfig.build.json", "compilerOptions": { + "preserveSymlinks": true, "baseUrl": ".", "paths": { /** @@ -46,6 +47,12 @@ "@webb-tools/interfaces/*": [ "packages/interfaces/*" ], + "@webb-tools/proposals": [ + "packages/proposals" + ], + "@webb-tools/proposals/*": [ + "packages/proposals/*" + ], "@webb-tools/tokens": [ "packages/tokens" ], diff --git a/yarn.lock b/yarn.lock index 896a27a5f..fd08923b7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1912,6 +1912,18 @@ tweetnacl "^1.0.3" tweetnacl-util "^0.15.1" +"@metamask/eth-sig-util@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@metamask/eth-sig-util/-/eth-sig-util-6.0.0.tgz#083321dc7285a9aa6e066db7c49be6e94c5e03a3" + integrity sha512-M0ezVz8lirXG1P6rHPzx+9i4zfhebCgVHE8XQT8VWxy/eUWllHQGcBcE8QmOusC7su55M4CMr9AyMIu0lx452g== + dependencies: + "@ethereumjs/util" "^8.0.6" + bn.js "^4.12.0" + ethereum-cryptography "^2.0.0" + ethjs-util "^0.1.6" + tweetnacl "^1.0.3" + tweetnacl-util "^0.15.1" + "@morgan-stanley/ts-mocking-bird@^0.6.2": version "0.6.4" resolved "https://registry.yarnpkg.com/@morgan-stanley/ts-mocking-bird/-/ts-mocking-bird-0.6.4.tgz#2e4b60d42957bab3b50b67dbf14c3da2f62a39f7" @@ -4968,14 +4980,6 @@ babel-preset-jest@^29.5.0: babel-plugin-jest-hoist "^29.5.0" babel-preset-current-node-syntax "^1.0.0" -babyjubjub@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/babyjubjub/-/babyjubjub-1.0.4.tgz#7b5ff0814005ea9c0a8bf63c93c5092232529bb6" - integrity sha512-R+EcKjxMSoctFCUtRUBKf3oDrnBrbbHUnt7u4LMGaERCx6/8+qfHZlqpBdsgAar9IvoPbDk0BLIj9lL9gfKCkw== - dependencies: - bignumber.js "^9.0.0" - buffer-reverse "^1.0.1" - balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" @@ -5286,11 +5290,6 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== -buffer-reverse@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/buffer-reverse/-/buffer-reverse-1.0.1.tgz#49283c8efa6f901bc01fa3304d06027971ae2f60" - integrity sha512-M87YIUBsZ6N924W57vDwT/aOu8hw7ZgdByz6ijksLjmHJELBASmYTTlNHRgjE+pTsT9oJXGaDSgqqwfdHotDUg== - buffer-to-arraybuffer@^0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/buffer-to-arraybuffer/-/buffer-to-arraybuffer-0.0.5.tgz#6064a40fa76eb43c723aba9ef8f6e1216d10511a" @@ -11355,7 +11354,7 @@ mocha@7.1.2: yargs-parser "13.1.2" yargs-unparser "1.6.0" -mocha@^10.0.0: +mocha@^10.0.0, mocha@^10.2.0: version "10.2.0" resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.2.0.tgz#1fd4a7c32ba5ac372e03a17eef435bd00e5c68b8" integrity sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==