Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add ERC1155 Pausable Extension #432

Merged
merged 18 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down
129 changes: 129 additions & 0 deletions docs/modules/ROOT/pages/erc1155-pausable.adoc
Original file line number Diff line number Diff line change
@@ -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<u8>> {
// ...
self.pausable.when_not_paused()?;
// ...
self.erc1155._mint(to, token_id, amount, &data)?;
Ok(())
}

fn mint_batch(
&mut self,
to: Address,
token_ids: Vec<U256>,
amounts: Vec<U256>,
data: Bytes,
) -> Result<(), Vec<u8>> {
// ...
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<u8>> {
// ...
self.pausable.when_not_paused()?;
// ...
self.erc1155.burn(account, token_id, value)?;
Ok(())
}

fn burn_batch(
&mut self,
account: Address,
token_ids: Vec<U256>,
values: Vec<U256>,
) -> Result<(), Vec<u8>> {
// ...
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<u8>> {
// ...
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<U256>,
values: Vec<U256>,
data: Bytes,
) -> Result<(), Vec<u8>> {
// ...
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;
0xNeshi marked this conversation as resolved.
Show resolved Hide resolved
}
}
----
2 changes: 2 additions & 0 deletions docs/modules/ROOT/pages/erc1155.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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.
4 changes: 2 additions & 2 deletions examples/erc1155-metadata-uri/src/constructor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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_;
}
Expand Down
6 changes: 6 additions & 0 deletions examples/erc1155/src/constructor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
60 changes: 43 additions & 17 deletions examples/erc1155/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -31,6 +33,7 @@ impl Erc1155Example {
amount: U256,
data: Bytes,
) -> Result<(), Vec<u8>> {
self.pausable.when_not_paused()?;
self.erc1155._mint(to, token_id, amount, &data)?;
Ok(())
}
Expand All @@ -42,30 +45,18 @@ impl Erc1155Example {
amounts: Vec<U256>,
data: Bytes,
) -> Result<(), Vec<u8>> {
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<u8>> {
self.erc1155
._operator_approvals
.setter(owner)
.setter(operator)
.set(approved);
Ok(())
}

fn burn(
&mut self,
account: Address,
token_id: U256,
value: U256,
) -> Result<(), Vec<u8>> {
self.pausable.when_not_paused()?;
self.erc1155.burn(account, token_id, value)?;
Ok(())
}
Expand All @@ -76,11 +67,46 @@ impl Erc1155Example {
token_ids: Vec<U256>,
values: Vec<U256>,
) -> Result<(), Vec<u8>> {
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<u8>> {
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<U256>,
values: Vec<U256>,
data: Bytes,
) -> Result<(), Vec<u8>> {
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<u8>> {
self.pausable.pause().map_err(|e| e.into())
}

fn unpause(&mut self) -> Result<(), Vec<u8>> {
self.pausable.unpause().map_err(|e| e.into())
}
}
12 changes: 10 additions & 2 deletions examples/erc1155/tests/abi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,29 @@ 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);
error ERC1155InvalidSender(address sender);
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);
#[derive(Debug, PartialEq)]
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);
}
);
Loading
Loading