git.delta.rocks / unique-network / refs/commits / 9871bd2fd605

difftreelog

feat balanceOfCross for RFT token

Trubnikov Sergey2023-04-18parent: #fc48e60.patch.diff
in: master

7 files changed

modifiedpallets/refungible/src/erc_token.rsdiffbeforeafterboth
--- a/pallets/refungible/src/erc_token.rs
+++ b/pallets/refungible/src/erc_token.rs
@@ -283,6 +283,16 @@
 			.map_err(dispatch_to_evm::<T>)?;
 		Ok(true)
 	}
+
+	/// @notice Balance of account
+	/// @param owner An cross address for whom to query the balance
+	/// @return The number of fingibles owned by `owner`, possibly zero
+	fn balance_of_cross(&self, owner: CrossAddress) -> Result<U256> {
+		self.consume_store_reads(1)?;
+		let balance = <Balance<T>>::get((self.id, self.1, owner.into_sub_cross_account::<T>()?));
+		Ok(balance.into())
+	}
+
 	/// @dev Function that changes total amount of the tokens.
 	///  Throws if `msg.sender` doesn't owns all of the tokens.
 	/// @param amount New total amount of the tokens.
modifiedpallets/refungible/src/stubs/UniqueRefungibleToken.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/refungible/src/stubs/UniqueRefungibleToken.soldiffbeforeafterboth
before · pallets/refungible/src/stubs/UniqueRefungibleToken.sol
1// SPDX-License-Identifier: OTHER2// This code is automatically generated34pragma solidity >=0.8.0 <0.9.0;56/// @dev common stubs holder7contract Dummy {8	uint8 dummy;9	string stub_error = "this contract is implemented in native";10}1112contract ERC165 is Dummy {13	function supportsInterface(bytes4 interfaceID) external view returns (bool) {14		require(false, stub_error);15		interfaceID;16		return true;17	}18}1920/// @dev the ERC-165 identifier for this interface is 0x5755c3f221contract ERC1633 is Dummy, ERC165 {22	/// @dev EVM selector for this function is: 0x80a54001,23	///  or in textual repr: parentToken()24	function parentToken() public view returns (address) {25		require(false, stub_error);26		dummy;27		return 0x0000000000000000000000000000000000000000;28	}2930	/// @dev EVM selector for this function is: 0xd7f083f3,31	///  or in textual repr: parentTokenId()32	function parentTokenId() public view returns (uint256) {33		require(false, stub_error);34		dummy;35		return 0;36	}37}3839/// @dev the ERC-165 identifier for this interface is 0x01d536fc40contract ERC20UniqueExtensions is Dummy, ERC165 {41	/// @dev Function to check the amount of tokens that an owner allowed to a spender.42	/// @param owner crossAddress The address which owns the funds.43	/// @param spender crossAddress The address which will spend the funds.44	/// @return A uint256 specifying the amount of tokens still available for the spender.45	/// @dev EVM selector for this function is: 0xe0af4bd7,46	///  or in textual repr: allowanceCross((address,uint256),(address,uint256))47	function allowanceCross(CrossAddress memory owner, CrossAddress memory spender) public view returns (uint256) {48		require(false, stub_error);49		owner;50		spender;51		dummy;52		return 0;53	}5455	// /// @dev Function that burns an amount of the token of a given account,56	// /// deducting from the sender's allowance for said account.57	// /// @param from The account whose tokens will be burnt.58	// /// @param amount The amount that will be burnt.59	// /// @dev EVM selector for this function is: 0x79cc6790,60	// ///  or in textual repr: burnFrom(address,uint256)61	// function burnFrom(address from, uint256 amount) public returns (bool) {62	// 	require(false, stub_error);63	// 	from;64	// 	amount;65	// 	dummy = 0;66	// 	return false;67	// }6869	/// @dev Function that burns an amount of the token of a given account,70	/// deducting from the sender's allowance for said account.71	/// @param from The account whose tokens will be burnt.72	/// @param amount The amount that will be burnt.73	/// @dev EVM selector for this function is: 0xbb2f5a58,74	///  or in textual repr: burnFromCross((address,uint256),uint256)75	function burnFromCross(CrossAddress memory from, uint256 amount) public returns (bool) {76		require(false, stub_error);77		from;78		amount;79		dummy = 0;80		return false;81	}8283	/// @dev Approve the passed address to spend the specified amount of tokens on behalf of `msg.sender`.84	/// Beware that changing an allowance with this method brings the risk that someone may use both the old85	/// and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this86	/// race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards:87	/// https://github.com/ethereum/EIPs/issues/20#issuecomment-26352472988	/// @param spender The crossaccount which will spend the funds.89	/// @param amount The amount of tokens to be spent.90	/// @dev EVM selector for this function is: 0x0ecd0ab0,91	///  or in textual repr: approveCross((address,uint256),uint256)92	function approveCross(CrossAddress memory spender, uint256 amount) public returns (bool) {93		require(false, stub_error);94		spender;95		amount;96		dummy = 0;97		return false;98	}99100	/// @dev Function that changes total amount of the tokens.101	///  Throws if `msg.sender` doesn't owns all of the tokens.102	/// @param amount New total amount of the tokens.103	/// @dev EVM selector for this function is: 0xd2418ca7,104	///  or in textual repr: repartition(uint256)105	function repartition(uint256 amount) public returns (bool) {106		require(false, stub_error);107		amount;108		dummy = 0;109		return false;110	}111112	/// @dev Transfer token for a specified address113	/// @param to The crossaccount to transfer to.114	/// @param amount The amount to be transferred.115	/// @dev EVM selector for this function is: 0x2ada85ff,116	///  or in textual repr: transferCross((address,uint256),uint256)117	function transferCross(CrossAddress memory to, uint256 amount) public returns (bool) {118		require(false, stub_error);119		to;120		amount;121		dummy = 0;122		return false;123	}124125	/// @dev Transfer tokens from one address to another126	/// @param from The address which you want to send tokens from127	/// @param to The address which you want to transfer to128	/// @param amount the amount of tokens to be transferred129	/// @dev EVM selector for this function is: 0xd5cf430b,130	///  or in textual repr: transferFromCross((address,uint256),(address,uint256),uint256)131	function transferFromCross(132		CrossAddress memory from,133		CrossAddress memory to,134		uint256 amount135	) public returns (bool) {136		require(false, stub_error);137		from;138		to;139		amount;140		dummy = 0;141		return false;142	}143}144145/// Cross account struct146struct CrossAddress {147	address eth;148	uint256 sub;149}150151/// @dev inlined interface152contract ERC20Events {153	event Transfer(address indexed from, address indexed to, uint256 value);154	event Approval(address indexed owner, address indexed spender, uint256 value);155}156157/// @title Standard ERC20 token158///159/// @dev Implementation of the basic standard token.160/// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md161/// @dev the ERC-165 identifier for this interface is 0x942e8b22162contract ERC20 is Dummy, ERC165, ERC20Events {163	/// @return the name of the token.164	/// @dev EVM selector for this function is: 0x06fdde03,165	///  or in textual repr: name()166	function name() public view returns (string memory) {167		require(false, stub_error);168		dummy;169		return "";170	}171172	/// @return the symbol of the token.173	/// @dev EVM selector for this function is: 0x95d89b41,174	///  or in textual repr: symbol()175	function symbol() public view returns (string memory) {176		require(false, stub_error);177		dummy;178		return "";179	}180181	/// @dev Total number of tokens in existence182	/// @dev EVM selector for this function is: 0x18160ddd,183	///  or in textual repr: totalSupply()184	function totalSupply() public view returns (uint256) {185		require(false, stub_error);186		dummy;187		return 0;188	}189190	/// @dev Not supported191	/// @dev EVM selector for this function is: 0x313ce567,192	///  or in textual repr: decimals()193	function decimals() public view returns (uint8) {194		require(false, stub_error);195		dummy;196		return 0;197	}198199	/// @dev Gets the balance of the specified address.200	/// @param owner The address to query the balance of.201	/// @return An uint256 representing the amount owned by the passed address.202	/// @dev EVM selector for this function is: 0x70a08231,203	///  or in textual repr: balanceOf(address)204	function balanceOf(address owner) public view returns (uint256) {205		require(false, stub_error);206		owner;207		dummy;208		return 0;209	}210211	/// @dev Transfer token for a specified address212	/// @param to The address to transfer to.213	/// @param amount The amount to be transferred.214	/// @dev EVM selector for this function is: 0xa9059cbb,215	///  or in textual repr: transfer(address,uint256)216	function transfer(address to, uint256 amount) public returns (bool) {217		require(false, stub_error);218		to;219		amount;220		dummy = 0;221		return false;222	}223224	/// @dev Transfer tokens from one address to another225	/// @param from address The address which you want to send tokens from226	/// @param to address The address which you want to transfer to227	/// @param amount uint256 the amount of tokens to be transferred228	/// @dev EVM selector for this function is: 0x23b872dd,229	///  or in textual repr: transferFrom(address,address,uint256)230	function transferFrom(231		address from,232		address to,233		uint256 amount234	) public returns (bool) {235		require(false, stub_error);236		from;237		to;238		amount;239		dummy = 0;240		return false;241	}242243	/// @dev Approve the passed address to spend the specified amount of tokens on behalf of `msg.sender`.244	/// Beware that changing an allowance with this method brings the risk that someone may use both the old245	/// and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this246	/// race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards:247	/// https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729248	/// @param spender The address which will spend the funds.249	/// @param amount The amount of tokens to be spent.250	/// @dev EVM selector for this function is: 0x095ea7b3,251	///  or in textual repr: approve(address,uint256)252	function approve(address spender, uint256 amount) public returns (bool) {253		require(false, stub_error);254		spender;255		amount;256		dummy = 0;257		return false;258	}259260	/// @dev Function to check the amount of tokens that an owner allowed to a spender.261	/// @param owner address The address which owns the funds.262	/// @param spender address The address which will spend the funds.263	/// @return A uint256 specifying the amount of tokens still available for the spender.264	/// @dev EVM selector for this function is: 0xdd62ed3e,265	///  or in textual repr: allowance(address,address)266	function allowance(address owner, address spender) public view returns (uint256) {267		require(false, stub_error);268		owner;269		spender;270		dummy;271		return 0;272	}273}274275contract UniqueRefungibleToken is Dummy, ERC165, ERC20, ERC20UniqueExtensions, ERC1633 {}
after · pallets/refungible/src/stubs/UniqueRefungibleToken.sol
1// SPDX-License-Identifier: OTHER2// This code is automatically generated34pragma solidity >=0.8.0 <0.9.0;56/// @dev common stubs holder7contract Dummy {8	uint8 dummy;9	string stub_error = "this contract is implemented in native";10}1112contract ERC165 is Dummy {13	function supportsInterface(bytes4 interfaceID) external view returns (bool) {14		require(false, stub_error);15		interfaceID;16		return true;17	}18}1920/// @dev the ERC-165 identifier for this interface is 0x5755c3f221contract ERC1633 is Dummy, ERC165 {22	/// @dev EVM selector for this function is: 0x80a54001,23	///  or in textual repr: parentToken()24	function parentToken() public view returns (address) {25		require(false, stub_error);26		dummy;27		return 0x0000000000000000000000000000000000000000;28	}2930	/// @dev EVM selector for this function is: 0xd7f083f3,31	///  or in textual repr: parentTokenId()32	function parentTokenId() public view returns (uint256) {33		require(false, stub_error);34		dummy;35		return 0;36	}37}3839/// @dev the ERC-165 identifier for this interface is 0xedd3a56440contract ERC20UniqueExtensions is Dummy, ERC165 {41	/// @dev Function to check the amount of tokens that an owner allowed to a spender.42	/// @param owner crossAddress The address which owns the funds.43	/// @param spender crossAddress The address which will spend the funds.44	/// @return A uint256 specifying the amount of tokens still available for the spender.45	/// @dev EVM selector for this function is: 0xe0af4bd7,46	///  or in textual repr: allowanceCross((address,uint256),(address,uint256))47	function allowanceCross(CrossAddress memory owner, CrossAddress memory spender) public view returns (uint256) {48		require(false, stub_error);49		owner;50		spender;51		dummy;52		return 0;53	}5455	// /// @dev Function that burns an amount of the token of a given account,56	// /// deducting from the sender's allowance for said account.57	// /// @param from The account whose tokens will be burnt.58	// /// @param amount The amount that will be burnt.59	// /// @dev EVM selector for this function is: 0x79cc6790,60	// ///  or in textual repr: burnFrom(address,uint256)61	// function burnFrom(address from, uint256 amount) public returns (bool) {62	// 	require(false, stub_error);63	// 	from;64	// 	amount;65	// 	dummy = 0;66	// 	return false;67	// }6869	/// @dev Function that burns an amount of the token of a given account,70	/// deducting from the sender's allowance for said account.71	/// @param from The account whose tokens will be burnt.72	/// @param amount The amount that will be burnt.73	/// @dev EVM selector for this function is: 0xbb2f5a58,74	///  or in textual repr: burnFromCross((address,uint256),uint256)75	function burnFromCross(CrossAddress memory from, uint256 amount) public returns (bool) {76		require(false, stub_error);77		from;78		amount;79		dummy = 0;80		return false;81	}8283	/// @dev Approve the passed address to spend the specified amount of tokens on behalf of `msg.sender`.84	/// Beware that changing an allowance with this method brings the risk that someone may use both the old85	/// and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this86	/// race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards:87	/// https://github.com/ethereum/EIPs/issues/20#issuecomment-26352472988	/// @param spender The crossaccount which will spend the funds.89	/// @param amount The amount of tokens to be spent.90	/// @dev EVM selector for this function is: 0x0ecd0ab0,91	///  or in textual repr: approveCross((address,uint256),uint256)92	function approveCross(CrossAddress memory spender, uint256 amount) public returns (bool) {93		require(false, stub_error);94		spender;95		amount;96		dummy = 0;97		return false;98	}99100	/// @notice Balance of account101	/// @param owner An cross address for whom to query the balance102	/// @return The number of fingibles owned by `owner`, possibly zero103	/// @dev EVM selector for this function is: 0xec069398,104	///  or in textual repr: balanceOfCross((address,uint256))105	function balanceOfCross(CrossAddress memory owner) public view returns (uint256) {106		require(false, stub_error);107		owner;108		dummy;109		return 0;110	}111112	/// @dev Function that changes total amount of the tokens.113	///  Throws if `msg.sender` doesn't owns all of the tokens.114	/// @param amount New total amount of the tokens.115	/// @dev EVM selector for this function is: 0xd2418ca7,116	///  or in textual repr: repartition(uint256)117	function repartition(uint256 amount) public returns (bool) {118		require(false, stub_error);119		amount;120		dummy = 0;121		return false;122	}123124	/// @dev Transfer token for a specified address125	/// @param to The crossaccount to transfer to.126	/// @param amount The amount to be transferred.127	/// @dev EVM selector for this function is: 0x2ada85ff,128	///  or in textual repr: transferCross((address,uint256),uint256)129	function transferCross(CrossAddress memory to, uint256 amount) public returns (bool) {130		require(false, stub_error);131		to;132		amount;133		dummy = 0;134		return false;135	}136137	/// @dev Transfer tokens from one address to another138	/// @param from The address which you want to send tokens from139	/// @param to The address which you want to transfer to140	/// @param amount the amount of tokens to be transferred141	/// @dev EVM selector for this function is: 0xd5cf430b,142	///  or in textual repr: transferFromCross((address,uint256),(address,uint256),uint256)143	function transferFromCross(144		CrossAddress memory from,145		CrossAddress memory to,146		uint256 amount147	) public returns (bool) {148		require(false, stub_error);149		from;150		to;151		amount;152		dummy = 0;153		return false;154	}155}156157/// Cross account struct158struct CrossAddress {159	address eth;160	uint256 sub;161}162163/// @dev inlined interface164contract ERC20Events {165	event Transfer(address indexed from, address indexed to, uint256 value);166	event Approval(address indexed owner, address indexed spender, uint256 value);167}168169/// @title Standard ERC20 token170///171/// @dev Implementation of the basic standard token.172/// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md173/// @dev the ERC-165 identifier for this interface is 0x942e8b22174contract ERC20 is Dummy, ERC165, ERC20Events {175	/// @return the name of the token.176	/// @dev EVM selector for this function is: 0x06fdde03,177	///  or in textual repr: name()178	function name() public view returns (string memory) {179		require(false, stub_error);180		dummy;181		return "";182	}183184	/// @return the symbol of the token.185	/// @dev EVM selector for this function is: 0x95d89b41,186	///  or in textual repr: symbol()187	function symbol() public view returns (string memory) {188		require(false, stub_error);189		dummy;190		return "";191	}192193	/// @dev Total number of tokens in existence194	/// @dev EVM selector for this function is: 0x18160ddd,195	///  or in textual repr: totalSupply()196	function totalSupply() public view returns (uint256) {197		require(false, stub_error);198		dummy;199		return 0;200	}201202	/// @dev Not supported203	/// @dev EVM selector for this function is: 0x313ce567,204	///  or in textual repr: decimals()205	function decimals() public view returns (uint8) {206		require(false, stub_error);207		dummy;208		return 0;209	}210211	/// @dev Gets the balance of the specified address.212	/// @param owner The address to query the balance of.213	/// @return An uint256 representing the amount owned by the passed address.214	/// @dev EVM selector for this function is: 0x70a08231,215	///  or in textual repr: balanceOf(address)216	function balanceOf(address owner) public view returns (uint256) {217		require(false, stub_error);218		owner;219		dummy;220		return 0;221	}222223	/// @dev Transfer token for a specified address224	/// @param to The address to transfer to.225	/// @param amount The amount to be transferred.226	/// @dev EVM selector for this function is: 0xa9059cbb,227	///  or in textual repr: transfer(address,uint256)228	function transfer(address to, uint256 amount) public returns (bool) {229		require(false, stub_error);230		to;231		amount;232		dummy = 0;233		return false;234	}235236	/// @dev Transfer tokens from one address to another237	/// @param from address The address which you want to send tokens from238	/// @param to address The address which you want to transfer to239	/// @param amount uint256 the amount of tokens to be transferred240	/// @dev EVM selector for this function is: 0x23b872dd,241	///  or in textual repr: transferFrom(address,address,uint256)242	function transferFrom(243		address from,244		address to,245		uint256 amount246	) public returns (bool) {247		require(false, stub_error);248		from;249		to;250		amount;251		dummy = 0;252		return false;253	}254255	/// @dev Approve the passed address to spend the specified amount of tokens on behalf of `msg.sender`.256	/// Beware that changing an allowance with this method brings the risk that someone may use both the old257	/// and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this258	/// race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards:259	/// https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729260	/// @param spender The address which will spend the funds.261	/// @param amount The amount of tokens to be spent.262	/// @dev EVM selector for this function is: 0x095ea7b3,263	///  or in textual repr: approve(address,uint256)264	function approve(address spender, uint256 amount) public returns (bool) {265		require(false, stub_error);266		spender;267		amount;268		dummy = 0;269		return false;270	}271272	/// @dev Function to check the amount of tokens that an owner allowed to a spender.273	/// @param owner address The address which owns the funds.274	/// @param spender address The address which will spend the funds.275	/// @return A uint256 specifying the amount of tokens still available for the spender.276	/// @dev EVM selector for this function is: 0xdd62ed3e,277	///  or in textual repr: allowance(address,address)278	function allowance(address owner, address spender) public view returns (uint256) {279		require(false, stub_error);280		owner;281		spender;282		dummy;283		return 0;284	}285}286287contract UniqueRefungibleToken is Dummy, ERC165, ERC20, ERC20UniqueExtensions, ERC1633 {}
modifiedruntime/common/ethereum/sponsoring/refungible.rsdiffbeforeafterboth
--- a/runtime/common/ethereum/sponsoring/refungible.rs
+++ b/runtime/common/ethereum/sponsoring/refungible.rs
@@ -343,9 +343,11 @@
 			ERC165Call(_, _) => None,
 
 			// Not sponsored
-			AllowanceCross { .. } | BurnFrom { .. } | BurnFromCross { .. } | Repartition { .. } => {
-				None
-			}
+			AllowanceCross { .. }
+			| BalanceOfCross { .. }
+			| BurnFrom { .. }
+			| BurnFromCross { .. }
+			| Repartition { .. } => None,
 
 			TransferCross { .. } | TransferFromCross { .. } => {
 				let RefungibleTokenHandle(handle, token_id) = token;
modifiedtests/src/eth/abi/reFungibleToken.jsondiffbeforeafterboth
--- a/tests/src/eth/abi/reFungibleToken.json
+++ b/tests/src/eth/abi/reFungibleToken.json
@@ -130,6 +130,23 @@
           { "internalType": "uint256", "name": "sub", "type": "uint256" }
         ],
         "internalType": "struct CrossAddress",
+        "name": "owner",
+        "type": "tuple"
+      }
+    ],
+    "name": "balanceOfCross",
+    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "eth", "type": "address" },
+          { "internalType": "uint256", "name": "sub", "type": "uint256" }
+        ],
+        "internalType": "struct CrossAddress",
         "name": "from",
         "type": "tuple"
       },
modifiedtests/src/eth/api/UniqueRefungibleToken.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueRefungibleToken.sol
+++ b/tests/src/eth/api/UniqueRefungibleToken.sol
@@ -23,7 +23,7 @@
 	function parentTokenId() external view returns (uint256);
 }
 
-/// @dev the ERC-165 identifier for this interface is 0x01d536fc
+/// @dev the ERC-165 identifier for this interface is 0xedd3a564
 interface ERC20UniqueExtensions is Dummy, ERC165 {
 	/// @dev Function to check the amount of tokens that an owner allowed to a spender.
 	/// @param owner crossAddress The address which owns the funds.
@@ -60,6 +60,13 @@
 	///  or in textual repr: approveCross((address,uint256),uint256)
 	function approveCross(CrossAddress memory spender, uint256 amount) external returns (bool);
 
+	/// @notice Balance of account
+	/// @param owner An cross address for whom to query the balance
+	/// @return The number of fingibles owned by `owner`, possibly zero
+	/// @dev EVM selector for this function is: 0xec069398,
+	///  or in textual repr: balanceOfCross((address,uint256))
+	function balanceOfCross(CrossAddress memory owner) external view returns (uint256);
+
 	/// @dev Function that changes total amount of the tokens.
 	///  Throws if `msg.sender` doesn't owns all of the tokens.
 	/// @param amount New total amount of the tokens.
modifiedtests/src/eth/reFungibleToken.test.tsdiffbeforeafterboth
--- a/tests/src/eth/reFungibleToken.test.ts
+++ b/tests/src/eth/reFungibleToken.test.ts
@@ -466,6 +466,21 @@
       expect(await collection.getTokenBalance(tokenId, {Substrate: ownerSub.address})).to.be.equal(150n);
     }
   });
+
+  itEth('Check balanceOfCross()', async ({helper}) => {
+    const collection = await helper.rft.mintCollection(alice, {});
+    const owner = await helper.ethCrossAccount.createAccountWithBalance(donor);
+    const {tokenId} = await collection.mintToken(alice, 200n, {Ethereum: owner.eth});
+    const tokenAddress = helper.ethAddress.fromTokenId(collection.collectionId, tokenId);
+    const tokenContract = await helper.ethNativeContract.rftToken(tokenAddress, owner.eth);
+
+    expect(BigInt(await tokenContract.methods.balanceOfCross(owner).call({from: owner.eth})) === 200n).to.be.true;
+
+    for (let i = 1n; i < 100n; i++) {
+      await tokenContract.methods.repartition(i).send({from: owner.eth});
+      expect(BigInt(await tokenContract.methods.balanceOfCross(owner).call({from: owner.eth})) === i).to.be.true;
+    }
+  });
 });
 
 describe('Refungible: Fees', () => {