diff --git a/CHANGELOG.md b/CHANGELOG.md index c3a3e318f..2bea83cf7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- `Erc1155Pausable`extension. #432 - `Erc1155UriStorage` extension. #431 - `VestingWallet` contract. #402 - `Erc1155Burnable` extension. #417 @@ -18,7 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Implement `MethodError` for `safe_erc20::Error`. #402 - Use `function_selector!` to calculate transfer type selector in `Erc1155`. #417 -- Update internal functions of `Erc721` and `Erc721Consecutive` accept reference to `Bytes`. #437 +- Update internal functions of `Erc721` and `Erc721Consecutive` accept reference to `Bytes`. #437 ### Fixed diff --git a/docs/modules/ROOT/pages/erc1155-pausable.adoc b/docs/modules/ROOT/pages/erc1155-pausable.adoc new file mode 100644 index 000000000..4d9dcd633 --- /dev/null +++ b/docs/modules/ROOT/pages/erc1155-pausable.adoc @@ -0,0 +1,129 @@ += ERC-1155 Pausable + +xref:erc1155.adoc[ERC-1155] token with pausable token transfers, minting, and burning. + +Useful for scenarios such as preventing trades until the end of an evaluation period, or having an emergency switch for freezing all token transfers in the event of a large bug. + +[[usage]] +== Usage + +In order to make your xref:erc1155.adoc[ERC-1155] token `pausable`, you need to use the https://docs.rs/openzeppelin-stylus/0.2.0-alpha.2/openzeppelin_stylus/utils/pausable/index.html[`Pausable`] contract and apply its mechanisms to ERC1155 token functions as follows: + +[source,rust] +---- +use openzeppelin_stylus::{ + token::erc1155::{Erc1155, IErc1155}, + utils::Pausable, +}; + +sol_storage! { + #[entrypoint] + struct Erc1155Example { + #[borrow] + Erc1155 erc1155; + #[borrow] + Pausable pausable; + } +} + +#[public] +#[inherit(Erc1155, Pausable)] +impl Erc1155Example { + fn mint( + &mut self, + to: Address, + token_id: U256, + amount: U256, + data: Bytes, + ) -> Result<(), Vec> { + // ... + self.pausable.when_not_paused()?; + // ... + self.erc1155._mint(to, token_id, amount, &data)?; + Ok(()) + } + + fn mint_batch( + &mut self, + to: Address, + token_ids: Vec, + amounts: Vec, + data: Bytes, + ) -> Result<(), Vec> { + // ... + self.pausable.when_not_paused()?; + // ... + self.erc1155._mint_batch(to, token_ids, amounts, &data)?; + Ok(()) + } + + fn burn( + &mut self, + account: Address, + token_id: U256, + value: U256, + ) -> Result<(), Vec> { + // ... + self.pausable.when_not_paused()?; + // ... + self.erc1155.burn(account, token_id, value)?; + Ok(()) + } + + fn burn_batch( + &mut self, + account: Address, + token_ids: Vec, + values: Vec, + ) -> Result<(), Vec> { + // ... + self.pausable.when_not_paused()?; + // ... + self.erc1155.burn_batch(account, token_ids, values)?; + Ok(()) + } + + fn safe_transfer_from( + &mut self, + from: Address, + to: Address, + id: U256, + value: U256, + data: Bytes, + ) -> Result<(), Vec> { + // ... + self.pausable.when_not_paused()?; + // ... + self.erc1155.safe_transfer_from(from, to, id, value, data)?; + Ok(()) + } + + fn safe_batch_transfer_from( + &mut self, + from: Address, + to: Address, + ids: Vec, + values: Vec, + data: Bytes, + ) -> Result<(), Vec> { + // ... + self.pausable.when_not_paused()?; + // ... + self.erc1155.safe_batch_transfer_from(from, to, ids, values, data)?; + Ok(()) + } +} +---- + +Additionally, you need to ensure proper initialization during xref:deploy.adoc[contract deployment]. Make sure to include the following code in your Solidity Constructor: + +[source,solidity] +---- +contract Erc1155Example { + bool private _paused; + + constructor() { + _paused = false; + } +} +---- diff --git a/docs/modules/ROOT/pages/erc1155.adoc b/docs/modules/ROOT/pages/erc1155.adoc index ce38a7e65..2fb611345 100644 --- a/docs/modules/ROOT/pages/erc1155.adoc +++ b/docs/modules/ROOT/pages/erc1155.adoc @@ -11,4 +11,6 @@ Additionally, there are multiple custom extensions, including: * xref:erc1155-metadata-uri.adoc[ERC-1155 Metadata URI]: Optional extension that adds a token metadata URI. +* xref:erc1155-pausable.adoc[ERC-1155 Pausable]: A primitive to pause contract operation like token transfers, minting and burning. + * xref:erc1155-uri-storage.adoc[ERC-1155 URI Storage]: A more flexible but more expensive way of storing URI metadata. diff --git a/examples/erc1155-metadata-uri/src/constructor.sol b/examples/erc1155-metadata-uri/src/constructor.sol index f39bac3b2..047d340be 100644 --- a/examples/erc1155-metadata-uri/src/constructor.sol +++ b/examples/erc1155-metadata-uri/src/constructor.sol @@ -4,12 +4,12 @@ pragma solidity ^0.8.24; contract Erc1155MetadataUriExample { mapping(address => mapping(uint256 => uint256)) private _balances; mapping(address => mapping(address => bool)) private _operatorApprovals; - + string private _uri; string private _baseUri; mapping(uint256 => string) _tokenUris; - + constructor(string memory uri_) { _uri = uri_; } diff --git a/examples/erc1155/src/constructor.sol b/examples/erc1155/src/constructor.sol index b4bec61c4..6e7aed244 100644 --- a/examples/erc1155/src/constructor.sol +++ b/examples/erc1155/src/constructor.sol @@ -4,4 +4,10 @@ pragma solidity ^0.8.24; contract Erc1155Example { mapping(address => mapping(uint256 => uint256)) private _balances; mapping(address => mapping(address => bool)) private _operatorApprovals; + + bool private _paused; + + constructor() { + _paused = false; + } } diff --git a/examples/erc1155/src/lib.rs b/examples/erc1155/src/lib.rs index 2fffc3817..778a66c71 100644 --- a/examples/erc1155/src/lib.rs +++ b/examples/erc1155/src/lib.rs @@ -5,8 +5,8 @@ use alloc::vec::Vec; use alloy_primitives::{Address, FixedBytes, U256}; use openzeppelin_stylus::{ - token::erc1155::{extensions::IErc1155Burnable, Erc1155}, - utils::introspection::erc165::IErc165, + token::erc1155::{extensions::IErc1155Burnable, Erc1155, IErc1155}, + utils::{introspection::erc165::IErc165, Pausable}, }; use stylus_sdk::{ abi::Bytes, @@ -18,11 +18,13 @@ sol_storage! { struct Erc1155Example { #[borrow] Erc1155 erc1155; + #[borrow] + Pausable pausable; } } #[public] -#[inherit(Erc1155)] +#[inherit(Erc1155, Pausable)] impl Erc1155Example { fn mint( &mut self, @@ -31,6 +33,7 @@ impl Erc1155Example { amount: U256, data: Bytes, ) -> Result<(), Vec> { + self.pausable.when_not_paused()?; self.erc1155._mint(to, token_id, amount, &data)?; Ok(()) } @@ -42,30 +45,18 @@ impl Erc1155Example { amounts: Vec, data: Bytes, ) -> Result<(), Vec> { + self.pausable.when_not_paused()?; self.erc1155._mint_batch(to, token_ids, amounts, &data)?; Ok(()) } - fn set_operator_approvals( - &mut self, - owner: Address, - operator: Address, - approved: bool, - ) -> Result<(), Vec> { - self.erc1155 - ._operator_approvals - .setter(owner) - .setter(operator) - .set(approved); - Ok(()) - } - fn burn( &mut self, account: Address, token_id: U256, value: U256, ) -> Result<(), Vec> { + self.pausable.when_not_paused()?; self.erc1155.burn(account, token_id, value)?; Ok(()) } @@ -76,11 +67,46 @@ impl Erc1155Example { token_ids: Vec, values: Vec, ) -> Result<(), Vec> { + self.pausable.when_not_paused()?; self.erc1155.burn_batch(account, token_ids, values)?; Ok(()) } + fn safe_transfer_from( + &mut self, + from: Address, + to: Address, + id: U256, + value: U256, + data: Bytes, + ) -> Result<(), Vec> { + self.pausable.when_not_paused()?; + self.erc1155.safe_transfer_from(from, to, id, value, data)?; + Ok(()) + } + + fn safe_batch_transfer_from( + &mut self, + from: Address, + to: Address, + ids: Vec, + values: Vec, + data: Bytes, + ) -> Result<(), Vec> { + self.pausable.when_not_paused()?; + self.erc1155.safe_batch_transfer_from(from, to, ids, values, data)?; + Ok(()) + } + fn supports_interface(interface_id: FixedBytes<4>) -> bool { Erc1155::supports_interface(interface_id) } + + fn pause(&mut self) -> Result<(), Vec> { + self.pausable.pause().map_err(|e| e.into()) + } + + fn unpause(&mut self) -> Result<(), Vec> { + self.pausable.unpause().map_err(|e| e.into()) + } } diff --git a/examples/erc1155/tests/abi/mod.rs b/examples/erc1155/tests/abi/mod.rs index e6a060de3..a5b85f4a3 100644 --- a/examples/erc1155/tests/abi/mod.rs +++ b/examples/erc1155/tests/abi/mod.rs @@ -15,8 +15,10 @@ sol!( function mintBatch(address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data) external; function burn(address account, uint256 id, uint256 value) external; function burnBatch(address account, uint256[] memory ids, uint256[] memory values) external; - function supportsInterface(bytes4 interfaceId) external view returns (bool); + function paused() external view returns (bool paused); + function pause() external; + function unpause() external; error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength); error ERC1155InvalidOperator(address operator); @@ -24,6 +26,8 @@ sol!( error ERC1155InvalidReceiver(address receiver); error ERC1155MissingApprovalForAll(address operator, address owner); error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId); + error EnforcedPause(); + error ExpectedPause(); #[derive(Debug, PartialEq)] event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value); @@ -31,5 +35,9 @@ sol!( event TransferBatch(address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values); #[derive(Debug, PartialEq)] event ApprovalForAll(address indexed account, address indexed operator, bool approved); -} + #[derive(Debug, PartialEq)] + event Paused(address account); + #[derive(Debug, PartialEq)] + event Unpaused(address account); + } ); diff --git a/examples/erc1155/tests/erc1155.rs b/examples/erc1155/tests/erc1155.rs index 61144f3a7..d9e27e9b0 100644 --- a/examples/erc1155/tests/erc1155.rs +++ b/examples/erc1155/tests/erc1155.rs @@ -23,7 +23,11 @@ fn random_values(size: usize) -> Vec { #[e2e::test] async fn constructs(alice: Account) -> eyre::Result<()> { let contract_addr = alice.as_deployer().deploy().await?.address()?; - Erc1155::new(contract_addr, &alice.wallet); + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let paused = contract.paused().call().await?.paused; + + assert!(!paused); Ok(()) } @@ -278,51 +282,44 @@ async fn errors_when_invalid_receiver_contract_in_mint( } #[e2e::test] -async fn mint_batch( - alice: Account, - bob: Account, - dave: Account, -) -> eyre::Result<()> { +async fn mint_batch(alice: Account) -> eyre::Result<()> { let contract_addr = alice.as_deployer().deploy().await?.address()?; let contract = Erc1155::new(contract_addr, &alice.wallet); let alice_addr = alice.address(); - let bob_addr = bob.address(); - let dave_addr = dave.address(); let token_ids = random_token_ids(3); let values = random_values(3); - let accounts = vec![alice_addr, bob_addr, dave_addr]; + let receipt = receipt!(contract.mintBatch( + alice_addr, + token_ids.clone(), + values.clone(), + vec![0, 1, 2, 3].into() + ))?; - for account in accounts { - let receipt = receipt!(contract.mintBatch( - account, - token_ids.clone(), - values.clone(), - vec![0, 1, 2, 3].into() - ))?; - - assert!(receipt.emits(Erc1155::TransferBatch { - operator: alice_addr, - from: Address::ZERO, - to: account, - ids: token_ids.clone(), - values: values.clone() - })); - - for (token_id, value) in token_ids.iter().zip(values.iter()) { - let Erc1155::balanceOfReturn { balance } = - contract.balanceOf(account, *token_id).call().await?; - assert_eq!(*value, balance); - } - - let Erc1155::balanceOfBatchReturn { balances } = contract - .balanceOfBatch(vec![account, account, account], token_ids.clone()) - .call() - .await?; + assert!(receipt.emits(Erc1155::TransferBatch { + operator: alice_addr, + from: Address::ZERO, + to: alice_addr, + ids: token_ids.clone(), + values: values.clone() + })); - assert_eq!(values, balances); + for (token_id, value) in token_ids.iter().zip(values.iter()) { + let Erc1155::balanceOfReturn { balance } = + contract.balanceOf(alice_addr, *token_id).call().await?; + assert_eq!(*value, balance); } + + let Erc1155::balanceOfBatchReturn { balances } = contract + .balanceOfBatch( + vec![alice_addr, alice_addr, alice_addr], + token_ids.clone(), + ) + .call() + .await?; + + assert_eq!(values, balances); Ok(()) } @@ -1892,3 +1889,286 @@ async fn support_interface(alice: Account) -> eyre::Result<()> { Ok(()) } + +// ============================================================================ +// Integration Tests: ERC-1155 Pausable Extension +// ============================================================================ + +#[e2e::test] +async fn pauses(alice: Account) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let receipt = receipt!(contract.pause())?; + + assert!(receipt.emits(Erc1155::Paused { account: alice.address() })); + + let paused = contract.paused().call().await?.paused; + + assert!(paused); + + Ok(()) +} + +#[e2e::test] +async fn pause_reverts_in_paused_state(alice: Account) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let _ = watch!(contract.pause())?; + + let err = + send!(contract.pause()).expect_err("should return `EnforcedPause`"); + + assert!(err.reverted_with(Erc1155::EnforcedPause {})); + + Ok(()) +} + +#[e2e::test] +async fn unpauses(alice: Account) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let _ = watch!(contract.pause())?; + + let receipt = receipt!(contract.unpause())?; + + assert!(receipt.emits(Erc1155::Unpaused { account: alice.address() })); + + let paused = contract.paused().call().await?.paused; + + assert!(!paused); + + Ok(()) +} + +#[e2e::test] +async fn unpause_reverts_in_unpaused_state(alice: Account) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let paused = contract.paused().call().await?.paused; + + assert!(!paused); + + let err = + send!(contract.unpause()).expect_err("should return `ExpectedPause`"); + + assert!(err.reverted_with(Erc1155::ExpectedPause {})); + + Ok(()) +} + +#[e2e::test] +async fn mint_reverts_in_paused_state(alice: Account) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let alice_addr = alice.address(); + let token_id = random_token_ids(1)[0]; + let value = random_values(1)[0]; + + let _ = watch!(contract.pause())?; + + let err = send!(contract.mint( + alice_addr, + token_id, + value, + vec![0, 1, 2, 3].into() + )) + .expect_err("should return `EnforcedPause`"); + + assert!(err.reverted_with(Erc1155::EnforcedPause {})); + + Ok(()) +} + +#[e2e::test] +async fn mint_batch_reverts_in_paused_state( + alice: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let alice_addr = alice.address(); + let token_ids = random_token_ids(3); + let values = random_values(3); + + let _ = watch!(contract.pause())?; + + let err = send!(contract.mintBatch( + alice_addr, + token_ids.clone(), + values.clone(), + vec![0, 1, 2, 3].into() + )) + .expect_err("should return `EnforcedPause`"); + + assert!(err.reverted_with(Erc1155::EnforcedPause {})); + + Ok(()) +} + +#[e2e::test] +async fn burn_reverts_in_paused_state(alice: Account) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let alice_addr = alice.address(); + let token_ids = random_token_ids(1); + let values = random_values(1); + + let _ = watch!(contract.mint( + alice_addr, + token_ids[0], + values[0], + vec![].into() + )); + + let _ = watch!(contract.pause())?; + + let err = send!(contract.burn(alice_addr, token_ids[0], values[0])) + .expect_err("should return `EnforcedPause`"); + + assert!(err.reverted_with(Erc1155::EnforcedPause {})); + + Ok(()) +} + +#[e2e::test] +async fn burn_batch_reverts_in_paused_state( + alice: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let alice_addr = alice.address(); + let token_ids = random_token_ids(4); + let values = random_values(4); + + let _ = watch!(contract.mintBatch( + alice_addr, + token_ids.clone(), + values.clone(), + vec![].into() + )); + + let _ = watch!(contract.pause())?; + + let err = send!(contract.burnBatch( + alice_addr, + token_ids.clone(), + values.clone() + )) + .expect_err("should return `EnforcedPause`"); + + assert!(err.reverted_with(Erc1155::EnforcedPause {})); + + Ok(()) +} + +#[e2e::test] +async fn safe_transfer_from_reverts_in_paused_state( + alice: Account, + bob: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let alice_addr = alice.address(); + let bob_addr = bob.address(); + let token_id = random_token_ids(1)[0]; + let value = random_values(1)[0]; + let _ = watch!(contract.mint( + alice_addr, + token_id, + value, + vec![0, 1, 2, 3].into() + )); + + let _ = watch!(contract.pause())?; + + let err = send!(contract.safeTransferFrom( + alice_addr, + bob_addr, + token_id, + value, + vec![].into() + )) + .expect_err("should return `EnforcedPause`"); + + assert!(err.reverted_with(Erc1155::EnforcedPause {})); + + Ok(()) +} + +#[e2e::test] +async fn safe_batch_transfer_from_reverts_in_paused_state( + alice: Account, + bob: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract_alice = Erc1155::new(contract_addr, &alice.wallet); + + let alice_addr = alice.address(); + let bob_addr = bob.address(); + let token_ids = random_token_ids(2); + let values = random_values(2); + + let _ = watch!(contract_alice.mintBatch( + alice_addr, + token_ids.clone(), + values.clone(), + vec![].into() + )); + + let _ = watch!(contract_alice.pause())?; + + let err = send!(contract_alice.safeBatchTransferFrom( + alice_addr, + bob_addr, + token_ids.clone(), + values.clone(), + vec![].into() + )) + .expect_err("should return `EnforcedPause`"); + + assert!(err.reverted_with(Erc1155::EnforcedPause {})); + + Ok(()) +} + +#[e2e::test] +async fn set_approval_for_all_does_not_revert_in_paused_state( + alice: Account, + bob: Account, +) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + let contract = Erc1155::new(contract_addr, &alice.wallet); + + let alice_addr = alice.address(); + let bob_addr = bob.address(); + + let _ = watch!(contract.pause())?; + + let approved_value = true; + let receipt = + receipt!(contract.setApprovalForAll(bob_addr, approved_value))?; + + assert!(receipt.emits(Erc1155::ApprovalForAll { + account: alice_addr, + operator: bob_addr, + approved: approved_value, + })); + + let Erc1155::isApprovedForAllReturn { approved } = + contract.isApprovedForAll(alice_addr, bob_addr).call().await?; + assert_eq!(approved_value, approved); + + Ok(()) +} diff --git a/examples/erc20/tests/erc20.rs b/examples/erc20/tests/erc20.rs index b16ffa9ca..d44cd010c 100644 --- a/examples/erc20/tests/erc20.rs +++ b/examples/erc20/tests/erc20.rs @@ -49,18 +49,20 @@ async fn constructs(alice: Account) -> Result<()> { .address()?; let contract = Erc20::new(contract_addr, &alice.wallet); - let Erc20::nameReturn { name } = contract.name().call().await?; - let Erc20::symbolReturn { symbol } = contract.symbol().call().await?; - let Erc20::capReturn { cap } = contract.cap().call().await?; - let Erc20::decimalsReturn { decimals } = contract.decimals().call().await?; - let Erc20::totalSupplyReturn { totalSupply: total_supply } = - contract.totalSupply().call().await?; + let name = contract.name().call().await?.name; + let symbol = contract.symbol().call().await?.symbol; + let cap = contract.cap().call().await?.cap; + let decimals = contract.decimals().call().await?.decimals; + let total_supply = contract.totalSupply().call().await?.totalSupply; + let paused = contract.paused().call().await?.paused; assert_eq!(name, TOKEN_NAME.to_owned()); assert_eq!(symbol, TOKEN_SYMBOL.to_owned()); assert_eq!(cap, CAP); assert_eq!(decimals, 10); assert_eq!(total_supply, U256::ZERO); + assert!(!paused); + Ok(()) } @@ -1057,13 +1059,34 @@ async fn pauses(alice: Account) -> eyre::Result<()> { assert!(receipt.emits(Erc20::Paused { account: alice.address() })); - let Erc20::pausedReturn { paused } = contract.paused().call().await?; + let paused = contract.paused().call().await?.paused; assert!(paused); Ok(()) } +#[e2e::test] +async fn pause_reverts_in_paused_state(alice: Account) -> eyre::Result<()> { + let contract_addr = alice + .as_deployer() + .with_default_constructor::() + .deploy() + .await? + .address()?; + + let contract = Erc20::new(contract_addr, &alice.wallet); + + let _ = watch!(contract.pause())?; + + let err = + send!(contract.pause()).expect_err("should return `EnforcedPause`"); + + assert!(err.reverted_with(Erc20::EnforcedPause {})); + + Ok(()) +} + #[e2e::test] async fn unpauses(alice: Account) -> eyre::Result<()> { let contract_addr = alice @@ -1080,13 +1103,36 @@ async fn unpauses(alice: Account) -> eyre::Result<()> { assert!(receipt.emits(Erc20::Unpaused { account: alice.address() })); - let Erc20::pausedReturn { paused } = contract.paused().call().await?; + let paused = contract.paused().call().await?.paused; assert!(!paused); Ok(()) } +#[e2e::test] +async fn unpause_reverts_in_unpaused_state(alice: Account) -> eyre::Result<()> { + let contract_addr = alice + .as_deployer() + .with_default_constructor::() + .deploy() + .await? + .address()?; + + let contract = Erc20::new(contract_addr, &alice.wallet); + + let paused = contract.paused().call().await?.paused; + + assert!(!paused); + + let err = + send!(contract.unpause()).expect_err("should return `ExpectedPause`"); + + assert!(err.reverted_with(Erc20::ExpectedPause {})); + + Ok(()) +} + #[e2e::test] async fn error_when_burn_in_paused_state(alice: Account) -> Result<()> { let contract_addr = alice @@ -1111,7 +1157,7 @@ async fn error_when_burn_in_paused_state(alice: Account) -> Result<()> { let _ = watch!(contract.pause())?; let err = - send!(contract.burn(value)).expect_err("should return EnforcedPause"); + send!(contract.burn(value)).expect_err("should return `EnforcedPause`"); assert!(err.reverted_with(Erc20::EnforcedPause {})); let Erc20::balanceOfReturn { balance } = @@ -1162,7 +1208,7 @@ async fn error_when_burn_from_in_paused_state( let _ = watch!(contract_alice.pause())?; let err = send!(contract_bob.burnFrom(alice_addr, value)) - .expect_err("should return EnforcedPause"); + .expect_err("should return `EnforcedPause`"); assert!(err.reverted_with(Erc20::EnforcedPause {})); let Erc20::balanceOfReturn { balance: alice_balance } = @@ -1204,7 +1250,7 @@ async fn error_when_mint_in_paused_state(alice: Account) -> Result<()> { let _ = watch!(contract.pause())?; let err = send!(contract.mint(alice_addr, uint!(1_U256))) - .expect_err("should return EnforcedPause"); + .expect_err("should return `EnforcedPause`"); assert!(err.reverted_with(Erc20::EnforcedPause {})); let Erc20::balanceOfReturn { balance } = @@ -1246,7 +1292,7 @@ async fn error_when_transfer_in_paused_state( let _ = watch!(contract_alice.pause())?; let err = send!(contract_alice.transfer(bob_addr, uint!(1_U256))) - .expect_err("should return EnforcedPause"); + .expect_err("should return `EnforcedPause`"); assert!(err.reverted_with(Erc20::EnforcedPause {})); let Erc20::balanceOfReturn { balance: alice_balance } = @@ -1297,7 +1343,7 @@ async fn error_when_transfer_from(alice: Account, bob: Account) -> Result<()> { let err = send!(contract_bob.transferFrom(alice_addr, bob_addr, uint!(1_U256))) - .expect_err("should return EnforcedPause"); + .expect_err("should return `EnforcedPause`"); assert!(err.reverted_with(Erc20::EnforcedPause {})); let Erc20::balanceOfReturn { balance: alice_balance } = diff --git a/examples/erc721/tests/erc721.rs b/examples/erc721/tests/erc721.rs index de471d101..6c390a242 100644 --- a/examples/erc721/tests/erc721.rs +++ b/examples/erc721/tests/erc721.rs @@ -22,7 +22,7 @@ async fn constructs(alice: Account) -> eyre::Result<()> { let contract_addr = alice.as_deployer().deploy().await?.address()?; let contract = Erc721::new(contract_addr, &alice.wallet); - let Erc721::pausedReturn { paused } = contract.paused().call().await?; + let paused = contract.paused().call().await?.paused; assert!(!paused); @@ -1374,6 +1374,22 @@ async fn pauses(alice: Account) -> eyre::Result<()> { Ok(()) } +#[e2e::test] +async fn pause_reverts_in_paused_state(alice: Account) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + + let contract = Erc721::new(contract_addr, &alice.wallet); + + let _ = watch!(contract.pause())?; + + let err = + send!(contract.pause()).expect_err("should return `EnforcedPause`"); + + assert!(err.reverted_with(Erc721::EnforcedPause {})); + + Ok(()) +} + #[e2e::test] async fn unpauses(alice: Account) -> eyre::Result<()> { let contract_addr = alice.as_deployer().deploy().await?.address()?; @@ -1392,6 +1408,24 @@ async fn unpauses(alice: Account) -> eyre::Result<()> { Ok(()) } +#[e2e::test] +async fn unpause_reverts_in_unpaused_state(alice: Account) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + + let contract = Erc721::new(contract_addr, &alice.wallet); + + let paused = contract.paused().call().await?.paused; + + assert!(!paused); + + let err = + send!(contract.unpause()).expect_err("should return `ExpectedPause`"); + + assert!(err.reverted_with(Erc721::ExpectedPause {})); + + Ok(()) +} + #[e2e::test] async fn error_when_burn_in_paused_state(alice: Account) -> eyre::Result<()> { let contract_addr = alice.as_deployer().deploy().await?.address()?; @@ -1407,7 +1441,7 @@ async fn error_when_burn_in_paused_state(alice: Account) -> eyre::Result<()> { let _ = watch!(contract.pause()); let err = send!(contract.burn(token_id)) - .expect_err("should return EnforcedPause"); + .expect_err("should return `EnforcedPause`"); assert!(err.reverted_with(Erc721::EnforcedPause {})); @@ -1434,7 +1468,7 @@ async fn error_when_mint_in_paused_state(alice: Account) -> eyre::Result<()> { let _ = watch!(contract.pause()); let err = send!(contract.mint(alice_addr, token_id)) - .expect_err("should return EnforcedPause"); + .expect_err("should return `EnforcedPause`"); assert!(err.reverted_with(Erc721::EnforcedPause {})); let err = contract @@ -1476,7 +1510,7 @@ async fn error_when_transfer_in_paused_state( let _ = watch!(contract.pause()); let err = send!(contract.transferFrom(alice_addr, bob_addr, token_id)) - .expect_err("should return EnforcedPause"); + .expect_err("should return `EnforcedPause`"); assert!(err.reverted_with(Erc721::EnforcedPause {})); let Erc721::ownerOfReturn { ownerOf } = @@ -1518,7 +1552,7 @@ async fn error_when_safe_transfer_in_paused_state( let err = send!(contract.safeTransferFrom_0(alice_addr, bob_addr, token_id)) - .expect_err("should return EnforcedPause"); + .expect_err("should return `EnforcedPause`"); assert!(err.reverted_with(Erc721::EnforcedPause {})); let Erc721::ownerOfReturn { ownerOf } = @@ -1564,7 +1598,7 @@ async fn error_when_safe_transfer_with_data_in_paused_state( token_id, fixed_bytes!("deadbeef").into() )) - .expect_err("should return EnforcedPause"); + .expect_err("should return `EnforcedPause`"); assert!(err.reverted_with(Erc721::EnforcedPause {})); let Erc721::ownerOfReturn { ownerOf } =