From 2c1140545c62cd21d3a95c237cd51d1c6b588e48 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Sat, 18 Dec 2021 10:41:34 -0800 Subject: [PATCH 01/18] Renamed directory contracts/erc20 -> contracts/moonstream --- contracts/{erc20 => moonstream}/ERC20Facet.sol | 0 contracts/{erc20 => moonstream}/ERC20Initializer.sol | 0 contracts/{erc20 => moonstream}/ERC20WithCommonStorage.sol | 0 contracts/{erc20 => moonstream}/LibERC20.sol | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename contracts/{erc20 => moonstream}/ERC20Facet.sol (100%) rename contracts/{erc20 => moonstream}/ERC20Initializer.sol (100%) rename contracts/{erc20 => moonstream}/ERC20WithCommonStorage.sol (100%) rename contracts/{erc20 => moonstream}/LibERC20.sol (100%) diff --git a/contracts/erc20/ERC20Facet.sol b/contracts/moonstream/ERC20Facet.sol similarity index 100% rename from contracts/erc20/ERC20Facet.sol rename to contracts/moonstream/ERC20Facet.sol diff --git a/contracts/erc20/ERC20Initializer.sol b/contracts/moonstream/ERC20Initializer.sol similarity index 100% rename from contracts/erc20/ERC20Initializer.sol rename to contracts/moonstream/ERC20Initializer.sol diff --git a/contracts/erc20/ERC20WithCommonStorage.sol b/contracts/moonstream/ERC20WithCommonStorage.sol similarity index 100% rename from contracts/erc20/ERC20WithCommonStorage.sol rename to contracts/moonstream/ERC20WithCommonStorage.sol diff --git a/contracts/erc20/LibERC20.sol b/contracts/moonstream/LibERC20.sol similarity index 100% rename from contracts/erc20/LibERC20.sol rename to contracts/moonstream/LibERC20.sol From b323c590d5abbdbe0eabe93108c9b540afc53271 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Sat, 18 Dec 2021 12:10:28 -0800 Subject: [PATCH 02/18] Removed "getController" method from LibERC20 It is useless. --- contracts/moonstream/LibERC20.sol | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/contracts/moonstream/LibERC20.sol b/contracts/moonstream/LibERC20.sol index ac195a3..d232e3e 100644 --- a/contracts/moonstream/LibERC20.sol +++ b/contracts/moonstream/LibERC20.sol @@ -1,4 +1,12 @@ // SPDX-License-Identifier: Apache-2.0 + +/** + * Authors: Moonstream Engineering (engineering@moonstream.to) + * GitHub: https://github.com/bugout-dev/dao + * + * Common storage structure and internal methods for Moonstream DAO ERC20 tokens. + */ + pragma solidity ^0.8.0; library LibERC20 { @@ -33,14 +41,6 @@ library LibERC20 { emit ControlTransferred(previousController, newController); } - function getController() - internal - view - returns (address contractController) - { - contractController = erc20Storage().controller; - } - function enforceIsController() internal view { ERC20Storage storage es = erc20Storage(); require(msg.sender == es.controller, "LibERC20: Must be controller"); From 87faf3db0064c7b82e5d227b72a233d39519865e Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Sat, 18 Dec 2021 12:10:53 -0800 Subject: [PATCH 03/18] Terminus smart contracts This represents the very first draft of Terminus. --- .../terminus/ERC1155WithTerminusStorage.sol | 555 ++++++++++++++++++ contracts/terminus/LibTerminus.sol | 115 ++++ contracts/terminus/TerminusFacet.sol | 46 ++ contracts/terminus/TerminusInitializer.sol | 26 + 4 files changed, 742 insertions(+) create mode 100644 contracts/terminus/ERC1155WithTerminusStorage.sol create mode 100644 contracts/terminus/LibTerminus.sol create mode 100644 contracts/terminus/TerminusFacet.sol create mode 100644 contracts/terminus/TerminusInitializer.sol diff --git a/contracts/terminus/ERC1155WithTerminusStorage.sol b/contracts/terminus/ERC1155WithTerminusStorage.sol new file mode 100644 index 0000000..143f473 --- /dev/null +++ b/contracts/terminus/ERC1155WithTerminusStorage.sol @@ -0,0 +1,555 @@ +// SPDX-License-Identifier: Apache-2.0 + +/** + * Authors: Moonstream Engineering (engineering@moonstream.to) + * GitHub: https://github.com/bugout-dev/dao + * + * An ERC1155 implementation which uses the Moonstream DAO common storage structure for proxies. + * EIP1155: https://eips.ethereum.org/EIPS/eip-1155 + * + * The Moonstream contract is used to delegate calls from an EIP2535 Diamond proxy. + * + * This implementation is adapted from the OpenZeppelin ERC1155 implementation: + * https://github.com/OpenZeppelin/openzeppelin-contracts/tree/6bd6b76d1156e20e45d1016f355d154141c7e5b9/contracts/token/ERC1155 + */ + +pragma solidity ^0.8.9; + +import "@openzeppelin-contracts/contracts/token/ERC1155/IERC1155.sol"; +import "@openzeppelin-contracts/contracts/token/ERC1155/IERC1155Receiver.sol"; +import "@openzeppelin-contracts/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol"; +import "@openzeppelin-contracts/contracts/utils/Address.sol"; +import "@openzeppelin-contracts/contracts/utils/Context.sol"; +import "@openzeppelin-contracts/contracts/utils/introspection/ERC165.sol"; +import "./LibTerminus.sol"; + +contract ERC1155WithTerminusStorage is + Context, + ERC165, + IERC1155, + IERC1155MetadataURI +{ + using Address for address; + + constructor() {} + + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) + public + view + virtual + override(ERC165, IERC165) + returns (bool) + { + return + interfaceId == type(IERC1155).interfaceId || + interfaceId == type(IERC1155MetadataURI).interfaceId || + super.supportsInterface(interfaceId); + } + + function uri(uint256 poolID) + public + view + virtual + override + returns (string memory) + { + return LibTerminus.terminusStorage().poolURI[poolID]; + } + + /** + * @dev See {IERC1155-balanceOf}. + * + * Requirements: + * + * - `account` cannot be the zero address. + */ + function balanceOf(address account, uint256 id) + public + view + virtual + override + returns (uint256) + { + require( + account != address(0), + "ERC1155: balance query for the zero address" + ); + return LibTerminus.terminusStorage().poolBalances[id][account]; + } + + /** + * @dev See {IERC1155-balanceOfBatch}. + * + * Requirements: + * + * - `accounts` and `ids` must have the same length. + */ + function balanceOfBatch(address[] memory accounts, uint256[] memory ids) + public + view + virtual + override + returns (uint256[] memory) + { + require( + accounts.length == ids.length, + "ERC1155: accounts and ids length mismatch" + ); + + uint256[] memory batchBalances = new uint256[](accounts.length); + + for (uint256 i = 0; i < accounts.length; ++i) { + batchBalances[i] = balanceOf(accounts[i], ids[i]); + } + + return batchBalances; + } + + /** + * @dev See {IERC1155-setApprovalForAll}. + */ + function setApprovalForAll(address operator, bool approved) + public + virtual + override + { + _setApprovalForAll(_msgSender(), operator, approved); + } + + /** + * @dev See {IERC1155-isApprovedForAll}. + */ + function isApprovedForAll(address account, address operator) + public + view + virtual + override + returns (bool) + { + return + LibTerminus.terminusStorage().globalOperatorApprovals[account][ + operator + ]; + } + + /** + * @dev See {IERC1155-safeTransferFrom}. + */ + function safeTransferFrom( + address from, + address to, + uint256 id, + uint256 amount, + bytes memory data + ) public virtual override { + require( + from == _msgSender() || isApprovedForAll(from, _msgSender()), + "ERC1155: caller is not owner nor approved" + ); + _safeTransferFrom(from, to, id, amount, data); + } + + /** + * @dev See {IERC1155-safeBatchTransferFrom}. + */ + function safeBatchTransferFrom( + address from, + address to, + uint256[] memory ids, + uint256[] memory amounts, + bytes memory data + ) public virtual override { + require( + from == _msgSender() || isApprovedForAll(from, _msgSender()), + "ERC1155: transfer caller is not owner nor approved" + ); + _safeBatchTransferFrom(from, to, ids, amounts, data); + } + + /** + * @dev Transfers `amount` tokens of token type `id` from `from` to `to`. + * + * Emits a {TransferSingle} event. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - `from` must have a balance of tokens of type `id` of at least `amount`. + * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the + * acceptance magic value. + */ + function _safeTransferFrom( + address from, + address to, + uint256 id, + uint256 amount, + bytes memory data + ) internal virtual { + require(to != address(0), "ERC1155: transfer to the zero address"); + + address operator = _msgSender(); + + _beforeTokenTransfer( + operator, + from, + to, + _asSingletonArray(id), + _asSingletonArray(amount), + data + ); + + LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage(); + + uint256 fromBalance = ts.poolBalances[id][from]; + require( + fromBalance >= amount, + "ERC1155: insufficient balance for transfer" + ); + unchecked { + ts.poolBalances[id][from] = fromBalance - amount; + } + ts.poolBalances[id][to] += amount; + + emit TransferSingle(operator, from, to, id, amount); + + _doSafeTransferAcceptanceCheck(operator, from, to, id, amount, data); + } + + /** + * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_safeTransferFrom}. + * + * Emits a {TransferBatch} event. + * + * Requirements: + * + * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the + * acceptance magic value. + */ + function _safeBatchTransferFrom( + address from, + address to, + uint256[] memory ids, + uint256[] memory amounts, + bytes memory data + ) internal virtual { + require( + ids.length == amounts.length, + "ERC1155: ids and amounts length mismatch" + ); + require(to != address(0), "ERC1155: transfer to the zero address"); + + address operator = _msgSender(); + + _beforeTokenTransfer(operator, from, to, ids, amounts, data); + + LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage(); + + for (uint256 i = 0; i < ids.length; ++i) { + uint256 id = ids[i]; + uint256 amount = amounts[i]; + + uint256 fromBalance = ts.poolBalances[id][from]; + require( + fromBalance >= amount, + "ERC1155: insufficient balance for transfer" + ); + unchecked { + ts.poolBalances[id][from] = fromBalance - amount; + } + ts.poolBalances[id][to] += amount; + } + + emit TransferBatch(operator, from, to, ids, amounts); + + _doSafeBatchTransferAcceptanceCheck( + operator, + from, + to, + ids, + amounts, + data + ); + } + + /** + * @dev Creates `amount` tokens of token type `id`, and assigns them to `to`. + * + * Emits a {TransferSingle} event. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the + * acceptance magic value. + */ + function _mint( + address to, + uint256 id, + uint256 amount, + bytes memory data + ) internal virtual { + require(to != address(0), "ERC1155: mint to the zero address"); + + address operator = _msgSender(); + + _beforeTokenTransfer( + operator, + address(0), + to, + _asSingletonArray(id), + _asSingletonArray(amount), + data + ); + + LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage(); + ts.poolBalances[id][to] += amount; + emit TransferSingle(operator, address(0), to, id, amount); + + _doSafeTransferAcceptanceCheck( + operator, + address(0), + to, + id, + amount, + data + ); + } + + /** + * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_mint}. + * + * Requirements: + * + * - `ids` and `amounts` must have the same length. + * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the + * acceptance magic value. + */ + function _mintBatch( + address to, + uint256[] memory ids, + uint256[] memory amounts, + bytes memory data + ) internal virtual { + require(to != address(0), "ERC1155: mint to the zero address"); + require( + ids.length == amounts.length, + "ERC1155: ids and amounts length mismatch" + ); + + address operator = _msgSender(); + + _beforeTokenTransfer(operator, address(0), to, ids, amounts, data); + + LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage(); + + for (uint256 i = 0; i < ids.length; i++) { + ts.poolBalances[ids[i]][to] += amounts[i]; + } + + emit TransferBatch(operator, address(0), to, ids, amounts); + + _doSafeBatchTransferAcceptanceCheck( + operator, + address(0), + to, + ids, + amounts, + data + ); + } + + /** + * @dev Destroys `amount` tokens of token type `id` from `from` + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `from` must have at least `amount` tokens of token type `id`. + */ + function _burn( + address from, + uint256 id, + uint256 amount + ) internal virtual { + require(from != address(0), "ERC1155: burn from the zero address"); + + address operator = _msgSender(); + + _beforeTokenTransfer( + operator, + from, + address(0), + _asSingletonArray(id), + _asSingletonArray(amount), + "" + ); + + LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage(); + uint256 fromBalance = ts.poolBalances[id][from]; + require(fromBalance >= amount, "ERC1155: burn amount exceeds balance"); + unchecked { + ts.poolBalances[id][from] = fromBalance - amount; + } + + emit TransferSingle(operator, from, address(0), id, amount); + } + + /** + * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_burn}. + * + * Requirements: + * + * - `ids` and `amounts` must have the same length. + */ + function _burnBatch( + address from, + uint256[] memory ids, + uint256[] memory amounts + ) internal virtual { + require(from != address(0), "ERC1155: burn from the zero address"); + require( + ids.length == amounts.length, + "ERC1155: ids and amounts length mismatch" + ); + + address operator = _msgSender(); + + _beforeTokenTransfer(operator, from, address(0), ids, amounts, ""); + LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage(); + + for (uint256 i = 0; i < ids.length; i++) { + uint256 id = ids[i]; + uint256 amount = amounts[i]; + + uint256 fromBalance = ts.poolBalances[id][from]; + require( + fromBalance >= amount, + "ERC1155: burn amount exceeds balance" + ); + unchecked { + ts.poolBalances[id][from] = fromBalance - amount; + } + } + + emit TransferBatch(operator, from, address(0), ids, amounts); + } + + /** + * @dev Approve `operator` to operate on all of `owner` tokens + * + * Emits a {ApprovalForAll} event. + */ + function _setApprovalForAll( + address owner, + address operator, + bool approved + ) internal virtual { + require(owner != operator, "ERC1155: setting approval status for self"); + LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage(); + ts.globalOperatorApprovals[owner][operator] = approved; + emit ApprovalForAll(owner, operator, approved); + } + + /** + * @dev Hook that is called before any token transfer. This includes minting + * and burning, as well as batched variants. + * + * The same hook is called on both single and batched variants. For single + * transfers, the length of the `id` and `amount` arrays will be 1. + * + * Calling conditions (for each `id` and `amount` pair): + * + * - When `from` and `to` are both non-zero, `amount` of ``from``'s tokens + * of token type `id` will be transferred to `to`. + * - When `from` is zero, `amount` tokens of token type `id` will be minted + * for `to`. + * - when `to` is zero, `amount` of ``from``'s tokens of token type `id` + * will be burned. + * - `from` and `to` are never both zero. + * - `ids` and `amounts` have the same, non-zero length. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _beforeTokenTransfer( + address operator, + address from, + address to, + uint256[] memory ids, + uint256[] memory amounts, + bytes memory data + ) internal virtual {} + + function _doSafeTransferAcceptanceCheck( + address operator, + address from, + address to, + uint256 id, + uint256 amount, + bytes memory data + ) private { + if (to.isContract()) { + try + IERC1155Receiver(to).onERC1155Received( + operator, + from, + id, + amount, + data + ) + returns (bytes4 response) { + if (response != IERC1155Receiver.onERC1155Received.selector) { + revert("ERC1155: ERC1155Receiver rejected tokens"); + } + } catch Error(string memory reason) { + revert(reason); + } catch { + revert("ERC1155: transfer to non ERC1155Receiver implementer"); + } + } + } + + function _doSafeBatchTransferAcceptanceCheck( + address operator, + address from, + address to, + uint256[] memory ids, + uint256[] memory amounts, + bytes memory data + ) private { + if (to.isContract()) { + try + IERC1155Receiver(to).onERC1155BatchReceived( + operator, + from, + ids, + amounts, + data + ) + returns (bytes4 response) { + if ( + response != IERC1155Receiver.onERC1155BatchReceived.selector + ) { + revert("ERC1155: ERC1155Receiver rejected tokens"); + } + } catch Error(string memory reason) { + revert(reason); + } catch { + revert("ERC1155: transfer to non ERC1155Receiver implementer"); + } + } + } + + function _asSingletonArray(uint256 element) + private + pure + returns (uint256[] memory) + { + uint256[] memory array = new uint256[](1); + array[0] = element; + + return array; + } +} diff --git a/contracts/terminus/LibTerminus.sol b/contracts/terminus/LibTerminus.sol new file mode 100644 index 0000000..243ef56 --- /dev/null +++ b/contracts/terminus/LibTerminus.sol @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: Apache-2.0 + +/** + * Authors: Moonstream Engineering (engineering@moonstream.to) + * GitHub: https://github.com/bugout-dev/dao + * + * Common storage structure and internal methods for Moonstream DAO Terminus contracts. + * As Terminus is an extension of ERC1155, this library can also be used to implement bare ERC1155 contracts + * using the common storage pattern (e.g. for use in diamond proxies). + */ + +// TODO(zomglings): Should we support EIP1761 in addition to ERC1155 or roll our own scopes and feature flags? +// https://eips.ethereum.org/EIPS/eip-1761 + +pragma solidity ^0.8.9; + +library LibTerminus { + bytes32 constant TERMINUS_STORAGE_POSITION = + keccak256("moonstreamdao.eth.storage.terminus"); + + struct TerminusStorage { + // Terminus administration + address controller; + bool isTerminusActive; + uint256 currentPoolID; + // Terminus pools + mapping(uint256 => address) poolController; + mapping(uint256 => bool) poolActive; + mapping(uint256 => string) poolURI; + mapping(uint256 => uint256) poolSupply; + mapping(uint256 => mapping(address => uint256)) poolBalances; + mapping(address => mapping(address => bool)) globalOperatorApprovals; + // TODO(zomglings): Add mappings for pool properties. E.g. poolTransferable, poolMintable, etc. + } + + function terminusStorage() + internal + pure + returns (TerminusStorage storage es) + { + bytes32 position = TERMINUS_STORAGE_POSITION; + assembly { + es.slot := position + } + } + + event ControlTransferred( + address indexed previousController, + address indexed newController + ); + + event PoolControlTransferred( + uint256 indexed poolID, + address indexed previousController, + address indexed newController + ); + + function setController(address newController) internal { + TerminusStorage storage ts = terminusStorage(); + address previousController = ts.controller; + ts.controller = newController; + emit ControlTransferred(previousController, newController); + } + + function enforceIsController() internal view { + TerminusStorage storage ts = terminusStorage(); + require(msg.sender == ts.controller, "LibTerminus: Must be controller"); + } + + function setTerminusActive(bool active) internal { + TerminusStorage storage ts = terminusStorage(); + ts.isTerminusActive = active; + } + + function setPoolController(uint256 poolID, address newController) internal { + TerminusStorage storage ts = terminusStorage(); + address previousController = ts.poolController[poolID]; + ts.poolController[poolID] = newController; + emit PoolControlTransferred(poolID, previousController, newController); + } + + function createPool() internal returns (uint256 poolID) { + TerminusStorage storage ts = terminusStorage(); + poolID = ts.currentPoolID + 1; + setPoolController(poolID, msg.sender); + ts.currentPoolID++; + } + + function enforceIsActive() internal view { + TerminusStorage storage ts = terminusStorage(); + require( + ts.isTerminusActive, + "LibTerminus: Terminus contract must be active" + ); + } + + function enforcePoolIsActive(uint256 poolID) internal view { + TerminusStorage storage ts = terminusStorage(); + require( + ts.poolActive[poolID], + "LibTerminus: Terminus pool must be active" + ); + } + + function enforcePoolIsController(uint256 poolID, address maybeController) + internal + view + { + TerminusStorage storage ts = terminusStorage(); + require( + ts.poolController[poolID] == maybeController, + "LibTerminus: Must be pool controller" + ); + } +} diff --git a/contracts/terminus/TerminusFacet.sol b/contracts/terminus/TerminusFacet.sol new file mode 100644 index 0000000..226e3f2 --- /dev/null +++ b/contracts/terminus/TerminusFacet.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: Apache-2.0 + +/** + * Authors: Moonstream Engineering (engineering@moonstream.to) + * GitHub: https://github.com/bugout-dev/dao + * + * This is an implementation of the ERC20 governance token for the Moonstream DAO. + */ + +pragma solidity ^0.8.0; + +import "./ERC1155WithTerminusStorage.sol"; +import "./LibTerminus.sol"; +import "../diamond/libraries/LibDiamond.sol"; + +contract TerminusFacet is ERC1155WithTerminusStorage { + constructor() {} + + function setURI(uint256 poolID, string memory poolURI) external { + LibTerminus.enforcePoolIsController(poolID, _msgSender()); + LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage(); + ts.poolURI[poolID] = poolURI; + } + + function mint( + address to, + uint256 poolID, + uint256 amount, + bytes memory data + ) external { + LibTerminus.enforcePoolIsController(poolID, msg.sender); + _mint(to, poolID, amount, data); + } + + function mintBatch( + address to, + uint256[] memory poolIDs, + uint256[] memory amounts, + bytes memory data + ) external { + for (uint256 i = 0; i < poolIDs.length; i++) { + LibTerminus.enforcePoolIsController(poolIDs[i], _msgSender()); + } + _mintBatch(to, poolIDs, amounts, data); + } +} diff --git a/contracts/terminus/TerminusInitializer.sol b/contracts/terminus/TerminusInitializer.sol new file mode 100644 index 0000000..c72a338 --- /dev/null +++ b/contracts/terminus/TerminusInitializer.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 + +/** + * Authors: Moonstream Engineering (engineering@moonstream.to) + * GitHub: https://github.com/bugout-dev/dao + * + * Initializer for Terminus contract. Used when mounting a new TerminusFacet onto its diamond proxy. + */ + +pragma solidity ^0.8.9; + +import "@openzeppelin-contracts/contracts/token/ERC1155/IERC1155.sol"; +import "@openzeppelin-contracts/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol"; +import "../diamond/libraries/LibDiamond.sol"; +import "./LibTerminus.sol"; + +contract TerminusInitializer { + function init() external { + LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); + ds.supportedInterfaces[type(IERC1155).interfaceId] = true; + ds.supportedInterfaces[type(IERC1155MetadataURI).interfaceId] = true; + + LibTerminus.TerminusStorage storage es = LibTerminus.terminusStorage(); + es.controller = msg.sender; + } +} From 7b3bd5d25534bd203edfc9dbd3ea747819445c47 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Sat, 18 Dec 2021 12:20:55 -0800 Subject: [PATCH 04/18] Added external "totalPools" and "createPool" on TerminusFacet --- contracts/terminus/LibTerminus.sol | 5 +++-- contracts/terminus/TerminusFacet.sol | 8 ++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/contracts/terminus/LibTerminus.sol b/contracts/terminus/LibTerminus.sol index 243ef56..f468a4d 100644 --- a/contracts/terminus/LibTerminus.sol +++ b/contracts/terminus/LibTerminus.sol @@ -79,11 +79,12 @@ library LibTerminus { emit PoolControlTransferred(poolID, previousController, newController); } - function createPool() internal returns (uint256 poolID) { + function createPool() internal returns (uint256) { TerminusStorage storage ts = terminusStorage(); - poolID = ts.currentPoolID + 1; + uint256 poolID = ts.currentPoolID + 1; setPoolController(poolID, msg.sender); ts.currentPoolID++; + return poolID; } function enforceIsActive() internal view { diff --git a/contracts/terminus/TerminusFacet.sol b/contracts/terminus/TerminusFacet.sol index 226e3f2..4f74e03 100644 --- a/contracts/terminus/TerminusFacet.sol +++ b/contracts/terminus/TerminusFacet.sol @@ -22,6 +22,14 @@ contract TerminusFacet is ERC1155WithTerminusStorage { ts.poolURI[poolID] = poolURI; } + function totalPools() external view returns (uint256) { + return LibTerminus.terminusStorage().currentPoolID; + } + + function createPool() external returns (uint256) { + return LibTerminus.createPool(); + } + function mint( address to, uint256 poolID, From dd986d36b334b042ac6972a95efae724d874812e Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Sat, 18 Dec 2021 12:21:38 -0800 Subject: [PATCH 05/18] Terminus functionality in "dao" cli --- dao/TerminusFacet.py | 509 +++++++++++++++++++++++++++++++++++++ dao/TerminusInitializer.py | 175 +++++++++++++ dao/cli.py | 12 +- dao/core.py | 10 + 4 files changed, 705 insertions(+), 1 deletion(-) create mode 100644 dao/TerminusFacet.py create mode 100644 dao/TerminusInitializer.py diff --git a/dao/TerminusFacet.py b/dao/TerminusFacet.py new file mode 100644 index 0000000..5b8da6d --- /dev/null +++ b/dao/TerminusFacet.py @@ -0,0 +1,509 @@ +# Code generated by moonworm : https://github.com/bugout-dev/moonworm +# Moonworm version : 0.1.8 + +import argparse +import json +import os +from pathlib import Path +from typing import Any, Dict, List, Optional, Union + +from brownie import Contract, network, project +from brownie.network.contract import ContractContainer +from eth_typing.evm import ChecksumAddress + + +PROJECT_DIRECTORY = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) +BUILD_DIRECTORY = os.path.join(PROJECT_DIRECTORY, "build", "contracts") + + +def boolean_argument_type(raw_value: str) -> bool: + TRUE_VALUES = ["1", "t", "y", "true", "yes"] + FALSE_VALUES = ["0", "f", "n", "false", "no"] + + if raw_value.lower() in TRUE_VALUES: + return True + elif raw_value.lower() in FALSE_VALUES: + return False + + raise ValueError( + f"Invalid boolean argument: {raw_value}. Value must be one of: {','.join(TRUE_VALUES + FALSE_VALUES)}" + ) + + +def bytes_argument_type(raw_value: str) -> bytes: + return raw_value.encode() + + +def get_abi_json(abi_name: str) -> List[Dict[str, Any]]: + abi_full_path = os.path.join(BUILD_DIRECTORY, f"{abi_name}.json") + if not os.path.isfile(abi_full_path): + raise IOError( + f"File does not exist: {abi_full_path}. Maybe you have to compile the smart contracts?" + ) + + with open(abi_full_path, "r") as ifp: + build = json.load(ifp) + + abi_json = build.get("abi") + if abi_json is None: + raise ValueError(f"Could not find ABI definition in: {abi_full_path}") + + return abi_json + + +def contract_from_build(abi_name: str) -> ContractContainer: + # This is workaround because brownie currently doesn't support loading the same project multiple + # times. This causes problems when using multiple contracts from the same project in the same + # python project. + PROJECT = project.main.Project("moonworm", Path(PROJECT_DIRECTORY)) + + abi_full_path = os.path.join(BUILD_DIRECTORY, f"{abi_name}.json") + if not os.path.isfile(abi_full_path): + raise IOError( + f"File does not exist: {abi_full_path}. Maybe you have to compile the smart contracts?" + ) + + with open(abi_full_path, "r") as ifp: + build = json.load(ifp) + + return ContractContainer(PROJECT, build) + + +class TerminusFacet: + def __init__(self, contract_address: Optional[ChecksumAddress]): + self.contract_name = "TerminusFacet" + self.address = contract_address + self.contract = None + self.abi = get_abi_json("TerminusFacet") + if self.address is not None: + self.contract: Optional[Contract] = Contract.from_abi( + self.contract_name, self.address, self.abi + ) + + def deploy(self, transaction_config): + contract_class = contract_from_build(self.contract_name) + deployed_contract = contract_class.deploy(transaction_config) + self.address = deployed_contract.address + self.contract = deployed_contract + + def assert_contract_is_instantiated(self) -> None: + if self.contract is None: + raise Exception("contract has not been instantiated") + + def balance_of(self, account: ChecksumAddress, id: int) -> Any: + self.assert_contract_is_instantiated() + return self.contract.balanceOf.call(account, id) + + def balance_of_batch(self, accounts: List, ids: List) -> Any: + self.assert_contract_is_instantiated() + return self.contract.balanceOfBatch.call(accounts, ids) + + def create_pool(self, transaction_config) -> Any: + self.assert_contract_is_instantiated() + return self.contract.createPool(transaction_config) + + def is_approved_for_all( + self, account: ChecksumAddress, operator: ChecksumAddress + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.isApprovedForAll.call(account, operator) + + def mint( + self, + to: ChecksumAddress, + pool_id: int, + amount: int, + data: bytes, + transaction_config, + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.mint(to, pool_id, amount, data, transaction_config) + + def mint_batch( + self, + to: ChecksumAddress, + pool_i_ds: List, + amounts: List, + data: bytes, + transaction_config, + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.mintBatch(to, pool_i_ds, amounts, data, transaction_config) + + def safe_batch_transfer_from( + self, + from_: ChecksumAddress, + to: ChecksumAddress, + ids: List, + amounts: List, + data: bytes, + transaction_config, + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.safeBatchTransferFrom( + from_, to, ids, amounts, data, transaction_config + ) + + def safe_transfer_from( + self, + from_: ChecksumAddress, + to: ChecksumAddress, + id: int, + amount: int, + data: bytes, + transaction_config, + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.safeTransferFrom( + from_, to, id, amount, data, transaction_config + ) + + def set_approval_for_all( + self, operator: ChecksumAddress, approved: bool, transaction_config + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.setApprovalForAll(operator, approved, transaction_config) + + def set_uri(self, pool_id: int, pool_uri: str, transaction_config) -> Any: + self.assert_contract_is_instantiated() + return self.contract.setURI(pool_id, pool_uri, transaction_config) + + def supports_interface(self, interface_id: bytes) -> Any: + self.assert_contract_is_instantiated() + return self.contract.supportsInterface.call(interface_id) + + def total_pools(self) -> Any: + self.assert_contract_is_instantiated() + return self.contract.totalPools.call() + + def uri(self, pool_id: int) -> Any: + self.assert_contract_is_instantiated() + return self.contract.uri.call(pool_id) + + +def get_transaction_config(args: argparse.Namespace) -> Dict[str, Any]: + signer = network.accounts.load(args.sender, args.password) + transaction_config: Dict[str, Any] = {"from": signer} + if args.gas_price is not None: + transaction_config["gas_price"] = args.gas_price + if args.confirmations is not None: + transaction_config["required_confs"] = args.confirmations + return transaction_config + + +def add_default_arguments(parser: argparse.ArgumentParser, transact: bool) -> None: + parser.add_argument( + "--network", required=True, help="Name of brownie network to connect to" + ) + parser.add_argument( + "--address", required=False, help="Address of deployed contract to connect to" + ) + if not transact: + return + parser.add_argument( + "--sender", required=True, help="Path to keystore file for transaction sender" + ) + parser.add_argument( + "--password", + required=False, + help="Password to keystore file (if you do not provide it, you will be prompted for it)", + ) + parser.add_argument( + "--gas-price", default=None, help="Gas price at which to submit transaction" + ) + parser.add_argument( + "--confirmations", + type=int, + default=None, + help="Number of confirmations to await before considering a transaction completed", + ) + + +def handle_deploy(args: argparse.Namespace) -> None: + network.connect(args.network) + transaction_config = get_transaction_config(args) + contract = TerminusFacet(None) + result = contract.deploy(transaction_config=transaction_config) + print(result) + + +def handle_balance_of(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = TerminusFacet(args.address) + result = contract.balance_of(account=args.account, id=args.id) + print(result) + + +def handle_balance_of_batch(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = TerminusFacet(args.address) + result = contract.balance_of_batch(accounts=args.accounts, ids=args.ids) + print(result) + + +def handle_create_pool(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = TerminusFacet(args.address) + transaction_config = get_transaction_config(args) + result = contract.create_pool(transaction_config=transaction_config) + print(result) + + +def handle_is_approved_for_all(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = TerminusFacet(args.address) + result = contract.is_approved_for_all(account=args.account, operator=args.operator) + print(result) + + +def handle_mint(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = TerminusFacet(args.address) + transaction_config = get_transaction_config(args) + result = contract.mint( + to=args.to, + pool_id=args.pool_id, + amount=args.amount, + data=args.data, + transaction_config=transaction_config, + ) + print(result) + + +def handle_mint_batch(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = TerminusFacet(args.address) + transaction_config = get_transaction_config(args) + result = contract.mint_batch( + to=args.to, + pool_i_ds=args.pool_i_ds, + amounts=args.amounts, + data=args.data, + transaction_config=transaction_config, + ) + print(result) + + +def handle_safe_batch_transfer_from(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = TerminusFacet(args.address) + transaction_config = get_transaction_config(args) + result = contract.safe_batch_transfer_from( + from_=args.from_arg, + to=args.to, + ids=args.ids, + amounts=args.amounts, + data=args.data, + transaction_config=transaction_config, + ) + print(result) + + +def handle_safe_transfer_from(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = TerminusFacet(args.address) + transaction_config = get_transaction_config(args) + result = contract.safe_transfer_from( + from_=args.from_arg, + to=args.to, + id=args.id, + amount=args.amount, + data=args.data, + transaction_config=transaction_config, + ) + print(result) + + +def handle_set_approval_for_all(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = TerminusFacet(args.address) + transaction_config = get_transaction_config(args) + result = contract.set_approval_for_all( + operator=args.operator, + approved=args.approved, + transaction_config=transaction_config, + ) + print(result) + + +def handle_set_uri(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = TerminusFacet(args.address) + transaction_config = get_transaction_config(args) + result = contract.set_uri( + pool_id=args.pool_id, + pool_uri=args.pool_uri, + transaction_config=transaction_config, + ) + print(result) + + +def handle_supports_interface(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = TerminusFacet(args.address) + result = contract.supports_interface(interface_id=args.interface_id) + print(result) + + +def handle_total_pools(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = TerminusFacet(args.address) + result = contract.total_pools() + print(result) + + +def handle_uri(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = TerminusFacet(args.address) + result = contract.uri(pool_id=args.pool_id) + print(result) + + +def generate_cli() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description="CLI for TerminusFacet") + parser.set_defaults(func=lambda _: parser.print_help()) + subcommands = parser.add_subparsers() + + deploy_parser = subcommands.add_parser("deploy") + add_default_arguments(deploy_parser, True) + deploy_parser.set_defaults(func=handle_deploy) + + balance_of_parser = subcommands.add_parser("balance-of") + add_default_arguments(balance_of_parser, False) + balance_of_parser.add_argument("--account", required=True, help="Type: address") + balance_of_parser.add_argument( + "--id", required=True, help="Type: uint256", type=int + ) + balance_of_parser.set_defaults(func=handle_balance_of) + + balance_of_batch_parser = subcommands.add_parser("balance-of-batch") + add_default_arguments(balance_of_batch_parser, False) + balance_of_batch_parser.add_argument( + "--accounts", required=True, help="Type: address[]", nargs="+" + ) + balance_of_batch_parser.add_argument( + "--ids", required=True, help="Type: uint256[]", nargs="+" + ) + balance_of_batch_parser.set_defaults(func=handle_balance_of_batch) + + create_pool_parser = subcommands.add_parser("create-pool") + add_default_arguments(create_pool_parser, True) + create_pool_parser.set_defaults(func=handle_create_pool) + + is_approved_for_all_parser = subcommands.add_parser("is-approved-for-all") + add_default_arguments(is_approved_for_all_parser, False) + is_approved_for_all_parser.add_argument( + "--account", required=True, help="Type: address" + ) + is_approved_for_all_parser.add_argument( + "--operator", required=True, help="Type: address" + ) + is_approved_for_all_parser.set_defaults(func=handle_is_approved_for_all) + + mint_parser = subcommands.add_parser("mint") + add_default_arguments(mint_parser, True) + mint_parser.add_argument("--to", required=True, help="Type: address") + mint_parser.add_argument("--pool-id", required=True, help="Type: uint256", type=int) + mint_parser.add_argument("--amount", required=True, help="Type: uint256", type=int) + mint_parser.add_argument( + "--data", required=True, help="Type: bytes", type=bytes_argument_type + ) + mint_parser.set_defaults(func=handle_mint) + + mint_batch_parser = subcommands.add_parser("mint-batch") + add_default_arguments(mint_batch_parser, True) + mint_batch_parser.add_argument("--to", required=True, help="Type: address") + mint_batch_parser.add_argument( + "--pool-i-ds", required=True, help="Type: uint256[]", nargs="+" + ) + mint_batch_parser.add_argument( + "--amounts", required=True, help="Type: uint256[]", nargs="+" + ) + mint_batch_parser.add_argument( + "--data", required=True, help="Type: bytes", type=bytes_argument_type + ) + mint_batch_parser.set_defaults(func=handle_mint_batch) + + safe_batch_transfer_from_parser = subcommands.add_parser("safe-batch-transfer-from") + add_default_arguments(safe_batch_transfer_from_parser, True) + safe_batch_transfer_from_parser.add_argument( + "--from-arg", required=True, help="Type: address" + ) + safe_batch_transfer_from_parser.add_argument( + "--to", required=True, help="Type: address" + ) + safe_batch_transfer_from_parser.add_argument( + "--ids", required=True, help="Type: uint256[]", nargs="+" + ) + safe_batch_transfer_from_parser.add_argument( + "--amounts", required=True, help="Type: uint256[]", nargs="+" + ) + safe_batch_transfer_from_parser.add_argument( + "--data", required=True, help="Type: bytes", type=bytes_argument_type + ) + safe_batch_transfer_from_parser.set_defaults(func=handle_safe_batch_transfer_from) + + safe_transfer_from_parser = subcommands.add_parser("safe-transfer-from") + add_default_arguments(safe_transfer_from_parser, True) + safe_transfer_from_parser.add_argument( + "--from-arg", required=True, help="Type: address" + ) + safe_transfer_from_parser.add_argument("--to", required=True, help="Type: address") + safe_transfer_from_parser.add_argument( + "--id", required=True, help="Type: uint256", type=int + ) + safe_transfer_from_parser.add_argument( + "--amount", required=True, help="Type: uint256", type=int + ) + safe_transfer_from_parser.add_argument( + "--data", required=True, help="Type: bytes", type=bytes_argument_type + ) + safe_transfer_from_parser.set_defaults(func=handle_safe_transfer_from) + + set_approval_for_all_parser = subcommands.add_parser("set-approval-for-all") + add_default_arguments(set_approval_for_all_parser, True) + set_approval_for_all_parser.add_argument( + "--operator", required=True, help="Type: address" + ) + set_approval_for_all_parser.add_argument( + "--approved", required=True, help="Type: bool", type=boolean_argument_type + ) + set_approval_for_all_parser.set_defaults(func=handle_set_approval_for_all) + + set_uri_parser = subcommands.add_parser("set-uri") + add_default_arguments(set_uri_parser, True) + set_uri_parser.add_argument( + "--pool-id", required=True, help="Type: uint256", type=int + ) + set_uri_parser.add_argument( + "--pool-uri", required=True, help="Type: string", type=str + ) + set_uri_parser.set_defaults(func=handle_set_uri) + + supports_interface_parser = subcommands.add_parser("supports-interface") + add_default_arguments(supports_interface_parser, False) + supports_interface_parser.add_argument( + "--interface-id", required=True, help="Type: bytes4", type=bytes_argument_type + ) + supports_interface_parser.set_defaults(func=handle_supports_interface) + + total_pools_parser = subcommands.add_parser("total-pools") + add_default_arguments(total_pools_parser, False) + total_pools_parser.set_defaults(func=handle_total_pools) + + uri_parser = subcommands.add_parser("uri") + add_default_arguments(uri_parser, False) + uri_parser.add_argument("--pool-id", required=True, help="Type: uint256", type=int) + uri_parser.set_defaults(func=handle_uri) + + return parser + + +def main() -> None: + parser = generate_cli() + args = parser.parse_args() + args.func(args) + + +if __name__ == "__main__": + main() diff --git a/dao/TerminusInitializer.py b/dao/TerminusInitializer.py new file mode 100644 index 0000000..5d2e862 --- /dev/null +++ b/dao/TerminusInitializer.py @@ -0,0 +1,175 @@ +# Code generated by moonworm : https://github.com/bugout-dev/moonworm +# Moonworm version : 0.1.8 + +import argparse +import json +import os +from pathlib import Path +from typing import Any, Dict, List, Optional, Union + +from brownie import Contract, network, project +from brownie.network.contract import ContractContainer +from eth_typing.evm import ChecksumAddress + + +PROJECT_DIRECTORY = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) +BUILD_DIRECTORY = os.path.join(PROJECT_DIRECTORY, "build", "contracts") + + +def boolean_argument_type(raw_value: str) -> bool: + TRUE_VALUES = ["1", "t", "y", "true", "yes"] + FALSE_VALUES = ["0", "f", "n", "false", "no"] + + if raw_value.lower() in TRUE_VALUES: + return True + elif raw_value.lower() in FALSE_VALUES: + return False + + raise ValueError( + f"Invalid boolean argument: {raw_value}. Value must be one of: {','.join(TRUE_VALUES + FALSE_VALUES)}" + ) + + +def bytes_argument_type(raw_value: str) -> bytes: + return raw_value.encode() + + +def get_abi_json(abi_name: str) -> List[Dict[str, Any]]: + abi_full_path = os.path.join(BUILD_DIRECTORY, f"{abi_name}.json") + if not os.path.isfile(abi_full_path): + raise IOError( + f"File does not exist: {abi_full_path}. Maybe you have to compile the smart contracts?" + ) + + with open(abi_full_path, "r") as ifp: + build = json.load(ifp) + + abi_json = build.get("abi") + if abi_json is None: + raise ValueError(f"Could not find ABI definition in: {abi_full_path}") + + return abi_json + + +def contract_from_build(abi_name: str) -> ContractContainer: + # This is workaround because brownie currently doesn't support loading the same project multiple + # times. This causes problems when using multiple contracts from the same project in the same + # python project. + PROJECT = project.main.Project("moonworm", Path(PROJECT_DIRECTORY)) + + abi_full_path = os.path.join(BUILD_DIRECTORY, f"{abi_name}.json") + if not os.path.isfile(abi_full_path): + raise IOError( + f"File does not exist: {abi_full_path}. Maybe you have to compile the smart contracts?" + ) + + with open(abi_full_path, "r") as ifp: + build = json.load(ifp) + + return ContractContainer(PROJECT, build) + + +class TerminusInitializer: + def __init__(self, contract_address: Optional[ChecksumAddress]): + self.contract_name = "TerminusInitializer" + self.address = contract_address + self.contract = None + self.abi = get_abi_json("TerminusInitializer") + if self.address is not None: + self.contract: Optional[Contract] = Contract.from_abi( + self.contract_name, self.address, self.abi + ) + + def deploy(self, transaction_config): + contract_class = contract_from_build(self.contract_name) + deployed_contract = contract_class.deploy(transaction_config) + self.address = deployed_contract.address + self.contract = deployed_contract + + def assert_contract_is_instantiated(self) -> None: + if self.contract is None: + raise Exception("contract has not been instantiated") + + def init(self, transaction_config) -> Any: + self.assert_contract_is_instantiated() + return self.contract.init(transaction_config) + + +def get_transaction_config(args: argparse.Namespace) -> Dict[str, Any]: + signer = network.accounts.load(args.sender, args.password) + transaction_config: Dict[str, Any] = {"from": signer} + if args.gas_price is not None: + transaction_config["gas_price"] = args.gas_price + if args.confirmations is not None: + transaction_config["required_confs"] = args.confirmations + return transaction_config + + +def add_default_arguments(parser: argparse.ArgumentParser, transact: bool) -> None: + parser.add_argument( + "--network", required=True, help="Name of brownie network to connect to" + ) + parser.add_argument( + "--address", required=False, help="Address of deployed contract to connect to" + ) + if not transact: + return + parser.add_argument( + "--sender", required=True, help="Path to keystore file for transaction sender" + ) + parser.add_argument( + "--password", + required=False, + help="Password to keystore file (if you do not provide it, you will be prompted for it)", + ) + parser.add_argument( + "--gas-price", default=None, help="Gas price at which to submit transaction" + ) + parser.add_argument( + "--confirmations", + type=int, + default=None, + help="Number of confirmations to await before considering a transaction completed", + ) + + +def handle_deploy(args: argparse.Namespace) -> None: + network.connect(args.network) + transaction_config = get_transaction_config(args) + contract = TerminusInitializer(None) + result = contract.deploy(transaction_config=transaction_config) + print(result) + + +def handle_init(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = TerminusInitializer(args.address) + transaction_config = get_transaction_config(args) + result = contract.init(transaction_config=transaction_config) + print(result) + + +def generate_cli() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description="CLI for TerminusInitializer") + parser.set_defaults(func=lambda _: parser.print_help()) + subcommands = parser.add_subparsers() + + deploy_parser = subcommands.add_parser("deploy") + add_default_arguments(deploy_parser, True) + deploy_parser.set_defaults(func=handle_deploy) + + init_parser = subcommands.add_parser("init") + add_default_arguments(init_parser, True) + init_parser.set_defaults(func=handle_init) + + return parser + + +def main() -> None: + parser = generate_cli() + args = parser.parse_args() + args.func(args) + + +if __name__ == "__main__": + main() diff --git a/dao/cli.py b/dao/cli.py index e9a68d6..bfe27e8 100644 --- a/dao/cli.py +++ b/dao/cli.py @@ -1,6 +1,6 @@ import argparse -from . import core, ERC20Facet, ERC20Initializer +from . import core, ERC20Facet, ERC20Initializer, TerminusFacet, TerminusInitializer def main(): @@ -23,6 +23,16 @@ def main(): add_help=False, ) + terminus_parser = TerminusFacet.generate_cli() + dao_subparsers.add_parser("terminus", parents=[terminus_parser], add_help=False) + + terminus_initializer_parser = TerminusInitializer.generate_cli() + dao_subparsers.add_parser( + "terminus-initializer", + parents=[terminus_initializer_parser], + add_help=False, + ) + args = parser.parse_args() args.func(args) diff --git a/dao/core.py b/dao/core.py index 9eb166b..0b1ab11 100644 --- a/dao/core.py +++ b/dao/core.py @@ -18,6 +18,8 @@ from . import ( ERC20Facet, ERC20Initializer, OwnershipFacet, + TerminusFacet, + TerminusInitializer, ) FACETS: Dict[str, Any] = { @@ -25,6 +27,7 @@ FACETS: Dict[str, Any] = { "DiamondLoupeFacet": DiamondLoupeFacet, "ERC20Facet": ERC20Facet, "OwnershipFacet": OwnershipFacet, + "TerminusFacet": TerminusFacet, } FACET_PRECEDENCE: List[str] = [ @@ -32,6 +35,7 @@ FACET_PRECEDENCE: List[str] = [ "OwnershipFacet", "DiamondLoupeFacet", "ERC20Facet", + "TerminusFacet", ] FACET_ACTIONS: Dict[str, int] = {"add": 0, "replace": 1, "remove": 2} @@ -107,6 +111,12 @@ def facet_cut( if initializer_address != ZERO_ADDRESS and action != "remove": erc20_initializer = ERC20Initializer.ERC20Initializer(initializer_address) calldata = erc20_initializer.contract.init.encode_input() + elif facet_name == "TerminusFacet": + if initializer_address != ZERO_ADDRESS and action != "remove": + terminus_initializer = TerminusInitializer.TerminusInitializer( + initializer_address + ) + calldata = terminus_initializer.contract.init.encode_input() diamond = DiamondCutFacet.DiamondCutFacet(diamond_address) transaction = diamond.diamond_cut( From cad68dcfa88ab64c6e26c9e4aae0ccfd24456223 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Sat, 18 Dec 2021 12:25:54 -0800 Subject: [PATCH 06/18] Added controller view methods on ERC20Facet and TerminusFacet --- contracts/moonstream/ERC20Facet.sol | 4 ++++ contracts/terminus/TerminusFacet.sol | 4 ++++ dao/ERC20Facet.py | 15 +++++++++++++++ dao/TerminusFacet.py | 15 +++++++++++++++ 4 files changed, 38 insertions(+) diff --git a/contracts/moonstream/ERC20Facet.sol b/contracts/moonstream/ERC20Facet.sol index 1f7d515..38ec9c2 100644 --- a/contracts/moonstream/ERC20Facet.sol +++ b/contracts/moonstream/ERC20Facet.sol @@ -16,6 +16,10 @@ import "../diamond/libraries/LibDiamond.sol"; contract ERC20Facet is ERC20WithCommonStorage { constructor() {} + function controller() external view returns (address) { + return LibERC20.erc20Storage().controller; + } + function mint(address account, uint256 amount) external { LibERC20.enforceIsController(); _mint(account, amount); diff --git a/contracts/terminus/TerminusFacet.sol b/contracts/terminus/TerminusFacet.sol index 4f74e03..187e48b 100644 --- a/contracts/terminus/TerminusFacet.sol +++ b/contracts/terminus/TerminusFacet.sol @@ -16,6 +16,10 @@ import "../diamond/libraries/LibDiamond.sol"; contract TerminusFacet is ERC1155WithTerminusStorage { constructor() {} + function controller() external view returns (address) { + return LibTerminus.terminusStorage().controller; + } + function setURI(uint256 poolID, string memory poolURI) external { LibTerminus.enforcePoolIsController(poolID, _msgSender()); LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage(); diff --git a/dao/ERC20Facet.py b/dao/ERC20Facet.py index 96c2f24..4b9b20e 100644 --- a/dao/ERC20Facet.py +++ b/dao/ERC20Facet.py @@ -102,6 +102,10 @@ class ERC20Facet: self.assert_contract_is_instantiated() return self.contract.balanceOf.call(account) + def controller(self) -> Any: + self.assert_contract_is_instantiated() + return self.contract.controller.call() + def decimals(self) -> Any: self.assert_contract_is_instantiated() return self.contract.decimals.call() @@ -227,6 +231,13 @@ def handle_balance_of(args: argparse.Namespace) -> None: print(result) +def handle_controller(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = ERC20Facet(args.address) + result = contract.controller() + print(result) + + def handle_decimals(args: argparse.Namespace) -> None: network.connect(args.network) contract = ERC20Facet(args.address) @@ -354,6 +365,10 @@ def generate_cli() -> argparse.ArgumentParser: balance_of_parser.add_argument("--account", required=True, help="Type: address") balance_of_parser.set_defaults(func=handle_balance_of) + controller_parser = subcommands.add_parser("controller") + add_default_arguments(controller_parser, False) + controller_parser.set_defaults(func=handle_controller) + decimals_parser = subcommands.add_parser("decimals") add_default_arguments(decimals_parser, False) decimals_parser.set_defaults(func=handle_decimals) diff --git a/dao/TerminusFacet.py b/dao/TerminusFacet.py index 5b8da6d..c84b2b6 100644 --- a/dao/TerminusFacet.py +++ b/dao/TerminusFacet.py @@ -98,6 +98,10 @@ class TerminusFacet: self.assert_contract_is_instantiated() return self.contract.balanceOfBatch.call(accounts, ids) + def controller(self) -> Any: + self.assert_contract_is_instantiated() + return self.contract.controller.call() + def create_pool(self, transaction_config) -> Any: self.assert_contract_is_instantiated() return self.contract.createPool(transaction_config) @@ -241,6 +245,13 @@ def handle_balance_of_batch(args: argparse.Namespace) -> None: print(result) +def handle_controller(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = TerminusFacet(args.address) + result = contract.controller() + print(result) + + def handle_create_pool(args: argparse.Namespace) -> None: network.connect(args.network) contract = TerminusFacet(args.address) @@ -386,6 +397,10 @@ def generate_cli() -> argparse.ArgumentParser: ) balance_of_batch_parser.set_defaults(func=handle_balance_of_batch) + controller_parser = subcommands.add_parser("controller") + add_default_arguments(controller_parser, False) + controller_parser.set_defaults(func=handle_controller) + create_pool_parser = subcommands.add_parser("create-pool") add_default_arguments(create_pool_parser, True) create_pool_parser.set_defaults(func=handle_create_pool) From aa18d54c6d521f1d72041c7f9d06bd7c045e6fdc Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Sat, 18 Dec 2021 12:30:09 -0800 Subject: [PATCH 07/18] Deployment test for terminus ERC20Facet.controller -> ERC20Facet.moonstreamController TerminusFacet.controller -> TerminusFacet.terminusController When both facets had view methods of the same name, `dao.core.facet_cut` was not mounting the `TerminusFacet.controller` method to the diamond because of its precedence rules. --- contracts/moonstream/ERC20Facet.sol | 2 +- contracts/terminus/TerminusFacet.sol | 2 +- dao/ERC20Facet.py | 30 +++++++++++------------ dao/TerminusFacet.py | 30 +++++++++++------------ dao/test_terminus.py | 36 ++++++++++++++++++++++++++++ 5 files changed, 68 insertions(+), 32 deletions(-) create mode 100644 dao/test_terminus.py diff --git a/contracts/moonstream/ERC20Facet.sol b/contracts/moonstream/ERC20Facet.sol index 38ec9c2..1e7f8d9 100644 --- a/contracts/moonstream/ERC20Facet.sol +++ b/contracts/moonstream/ERC20Facet.sol @@ -16,7 +16,7 @@ import "../diamond/libraries/LibDiamond.sol"; contract ERC20Facet is ERC20WithCommonStorage { constructor() {} - function controller() external view returns (address) { + function moonstreamController() external view returns (address) { return LibERC20.erc20Storage().controller; } diff --git a/contracts/terminus/TerminusFacet.sol b/contracts/terminus/TerminusFacet.sol index 187e48b..94f9c2b 100644 --- a/contracts/terminus/TerminusFacet.sol +++ b/contracts/terminus/TerminusFacet.sol @@ -16,7 +16,7 @@ import "../diamond/libraries/LibDiamond.sol"; contract TerminusFacet is ERC1155WithTerminusStorage { constructor() {} - function controller() external view returns (address) { + function terminusController() external view returns (address) { return LibTerminus.terminusStorage().controller; } diff --git a/dao/ERC20Facet.py b/dao/ERC20Facet.py index 4b9b20e..dd1d9e7 100644 --- a/dao/ERC20Facet.py +++ b/dao/ERC20Facet.py @@ -102,10 +102,6 @@ class ERC20Facet: self.assert_contract_is_instantiated() return self.contract.balanceOf.call(account) - def controller(self) -> Any: - self.assert_contract_is_instantiated() - return self.contract.controller.call() - def decimals(self) -> Any: self.assert_contract_is_instantiated() return self.contract.decimals.call() @@ -128,6 +124,10 @@ class ERC20Facet: self.assert_contract_is_instantiated() return self.contract.mint(account, amount, transaction_config) + def moonstream_controller(self) -> Any: + self.assert_contract_is_instantiated() + return self.contract.moonstreamController.call() + def name(self) -> Any: self.assert_contract_is_instantiated() return self.contract.name.call() @@ -231,13 +231,6 @@ def handle_balance_of(args: argparse.Namespace) -> None: print(result) -def handle_controller(args: argparse.Namespace) -> None: - network.connect(args.network) - contract = ERC20Facet(args.address) - result = contract.controller() - print(result) - - def handle_decimals(args: argparse.Namespace) -> None: network.connect(args.network) contract = ERC20Facet(args.address) @@ -279,6 +272,13 @@ def handle_mint(args: argparse.Namespace) -> None: print(result) +def handle_moonstream_controller(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = ERC20Facet(args.address) + result = contract.moonstream_controller() + print(result) + + def handle_name(args: argparse.Namespace) -> None: network.connect(args.network) contract = ERC20Facet(args.address) @@ -365,10 +365,6 @@ def generate_cli() -> argparse.ArgumentParser: balance_of_parser.add_argument("--account", required=True, help="Type: address") balance_of_parser.set_defaults(func=handle_balance_of) - controller_parser = subcommands.add_parser("controller") - add_default_arguments(controller_parser, False) - controller_parser.set_defaults(func=handle_controller) - decimals_parser = subcommands.add_parser("decimals") add_default_arguments(decimals_parser, False) decimals_parser.set_defaults(func=handle_decimals) @@ -399,6 +395,10 @@ def generate_cli() -> argparse.ArgumentParser: mint_parser.add_argument("--amount", required=True, help="Type: uint256", type=int) mint_parser.set_defaults(func=handle_mint) + moonstream_controller_parser = subcommands.add_parser("moonstream-controller") + add_default_arguments(moonstream_controller_parser, False) + moonstream_controller_parser.set_defaults(func=handle_moonstream_controller) + name_parser = subcommands.add_parser("name") add_default_arguments(name_parser, False) name_parser.set_defaults(func=handle_name) diff --git a/dao/TerminusFacet.py b/dao/TerminusFacet.py index c84b2b6..c71adc3 100644 --- a/dao/TerminusFacet.py +++ b/dao/TerminusFacet.py @@ -98,10 +98,6 @@ class TerminusFacet: self.assert_contract_is_instantiated() return self.contract.balanceOfBatch.call(accounts, ids) - def controller(self) -> Any: - self.assert_contract_is_instantiated() - return self.contract.controller.call() - def create_pool(self, transaction_config) -> Any: self.assert_contract_is_instantiated() return self.contract.createPool(transaction_config) @@ -176,6 +172,10 @@ class TerminusFacet: self.assert_contract_is_instantiated() return self.contract.supportsInterface.call(interface_id) + def terminus_controller(self) -> Any: + self.assert_contract_is_instantiated() + return self.contract.terminusController.call() + def total_pools(self) -> Any: self.assert_contract_is_instantiated() return self.contract.totalPools.call() @@ -245,13 +245,6 @@ def handle_balance_of_batch(args: argparse.Namespace) -> None: print(result) -def handle_controller(args: argparse.Namespace) -> None: - network.connect(args.network) - contract = TerminusFacet(args.address) - result = contract.controller() - print(result) - - def handle_create_pool(args: argparse.Namespace) -> None: network.connect(args.network) contract = TerminusFacet(args.address) @@ -356,6 +349,13 @@ def handle_supports_interface(args: argparse.Namespace) -> None: print(result) +def handle_terminus_controller(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = TerminusFacet(args.address) + result = contract.terminus_controller() + print(result) + + def handle_total_pools(args: argparse.Namespace) -> None: network.connect(args.network) contract = TerminusFacet(args.address) @@ -397,10 +397,6 @@ def generate_cli() -> argparse.ArgumentParser: ) balance_of_batch_parser.set_defaults(func=handle_balance_of_batch) - controller_parser = subcommands.add_parser("controller") - add_default_arguments(controller_parser, False) - controller_parser.set_defaults(func=handle_controller) - create_pool_parser = subcommands.add_parser("create-pool") add_default_arguments(create_pool_parser, True) create_pool_parser.set_defaults(func=handle_create_pool) @@ -502,6 +498,10 @@ def generate_cli() -> argparse.ArgumentParser: ) supports_interface_parser.set_defaults(func=handle_supports_interface) + terminus_controller_parser = subcommands.add_parser("terminus-controller") + add_default_arguments(terminus_controller_parser, False) + terminus_controller_parser.set_defaults(func=handle_terminus_controller) + total_pools_parser = subcommands.add_parser("total-pools") add_default_arguments(total_pools_parser, False) total_pools_parser.set_defaults(func=handle_total_pools) diff --git a/dao/test_terminus.py b/dao/test_terminus.py new file mode 100644 index 0000000..aaa2f39 --- /dev/null +++ b/dao/test_terminus.py @@ -0,0 +1,36 @@ +import unittest + +from brownie import accounts +import brownie + +from . import TerminusFacet, TerminusInitializer +from .core import ZERO_ADDRESS, facet_cut +from .test_core import MoonstreamDAOTestCase, MoonstreamDAOFullTestCase + + +class TestDeployment(MoonstreamDAOTestCase): + def test_add_and_replace(self): + initializer = TerminusInitializer.TerminusInitializer(None) + initializer.deploy({"from": accounts[0]}) + + terminus_facet = TerminusFacet.TerminusFacet(None) + terminus_facet.deploy({"from": accounts[0]}) + + diamond_address = self.contracts["Diamond"] + facet_cut( + diamond_address, + "TerminusFacet", + terminus_facet.address, + "add", + {"from": accounts[0]}, + initializer.address, + ) + + diamond_terminus = TerminusFacet.TerminusFacet(diamond_address) + + controller = diamond_terminus.terminus_controller() + self.assertEqual(controller, accounts[0].address) + + +if __name__ == "__main__": + unittest.main() From 2747d63067f89cabf315e42dc3be7e3571bba706 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Sat, 18 Dec 2021 12:42:21 -0800 Subject: [PATCH 08/18] Test for pool creation Also added the `terminusPoolController` view method on the TerminusFacet. --- contracts/terminus/TerminusFacet.sol | 8 ++++++++ dao/TerminusFacet.py | 18 +++++++++++++++++ dao/test_core.py | 30 +++++++++++++++++++++++++++- dao/test_moonstream.py | 4 ++-- dao/test_terminus.py | 19 +++++++++++++++--- 5 files changed, 73 insertions(+), 6 deletions(-) diff --git a/contracts/terminus/TerminusFacet.sol b/contracts/terminus/TerminusFacet.sol index 94f9c2b..d004d95 100644 --- a/contracts/terminus/TerminusFacet.sol +++ b/contracts/terminus/TerminusFacet.sol @@ -34,6 +34,14 @@ contract TerminusFacet is ERC1155WithTerminusStorage { return LibTerminus.createPool(); } + function terminusPoolController(uint256 poolID) + external + view + returns (address) + { + return LibTerminus.terminusStorage().poolController[poolID]; + } + function mint( address to, uint256 poolID, diff --git a/dao/TerminusFacet.py b/dao/TerminusFacet.py index c71adc3..0c0e94b 100644 --- a/dao/TerminusFacet.py +++ b/dao/TerminusFacet.py @@ -176,6 +176,10 @@ class TerminusFacet: self.assert_contract_is_instantiated() return self.contract.terminusController.call() + def terminus_pool_controller(self, pool_id: int) -> Any: + self.assert_contract_is_instantiated() + return self.contract.terminusPoolController.call(pool_id) + def total_pools(self) -> Any: self.assert_contract_is_instantiated() return self.contract.totalPools.call() @@ -356,6 +360,13 @@ def handle_terminus_controller(args: argparse.Namespace) -> None: print(result) +def handle_terminus_pool_controller(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = TerminusFacet(args.address) + result = contract.terminus_pool_controller(pool_id=args.pool_id) + print(result) + + def handle_total_pools(args: argparse.Namespace) -> None: network.connect(args.network) contract = TerminusFacet(args.address) @@ -502,6 +513,13 @@ def generate_cli() -> argparse.ArgumentParser: add_default_arguments(terminus_controller_parser, False) terminus_controller_parser.set_defaults(func=handle_terminus_controller) + terminus_pool_controller_parser = subcommands.add_parser("terminus-pool-controller") + add_default_arguments(terminus_pool_controller_parser, False) + terminus_pool_controller_parser.add_argument( + "--pool-id", required=True, help="Type: uint256", type=int + ) + terminus_pool_controller_parser.set_defaults(func=handle_terminus_pool_controller) + total_pools_parser = subcommands.add_parser("total-pools") add_default_arguments(total_pools_parser, False) total_pools_parser.set_defaults(func=handle_total_pools) diff --git a/dao/test_core.py b/dao/test_core.py index 0a39eed..6d6c925 100644 --- a/dao/test_core.py +++ b/dao/test_core.py @@ -5,6 +5,8 @@ from brownie import accounts, network from .core import facet_cut, gogogo from .ERC20Facet import ERC20Facet from .ERC20Initializer import ERC20Initializer +from . import TerminusFacet +from . import TerminusInitializer class MoonstreamDAOTestCase(unittest.TestCase): @@ -17,7 +19,7 @@ class MoonstreamDAOTestCase(unittest.TestCase): cls.contracts = gogogo(accounts[0], {"from": accounts[0]}) -class MoonstreamDAOFullTestCase(MoonstreamDAOTestCase): +class MoonstreamTokenTestCase(MoonstreamDAOTestCase): @classmethod def setUpClass(cls) -> None: super().setUpClass() @@ -43,6 +45,32 @@ class MoonstreamDAOFullTestCase(MoonstreamDAOTestCase): cls.erc20_facet = erc20_facet.address +class TerminusTestCase(MoonstreamDAOTestCase): + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + + # Deploy Terminus + initializer = TerminusInitializer.TerminusInitializer(None) + initializer.deploy({"from": accounts[0]}) + + terminus_facet = TerminusFacet.TerminusFacet(None) + terminus_facet.deploy({"from": accounts[0]}) + + diamond_address = cls.contracts["Diamond"] + facet_cut( + diamond_address, + "TerminusFacet", + terminus_facet.address, + "add", + {"from": accounts[0]}, + initializer.address, + ) + + cls.terminus_initializer = initializer.address + cls.terminus_facet = terminus_facet.address + + class TestCoreDeployment(MoonstreamDAOTestCase): def test_gogogo(self): self.assertIn("DiamondCutFacet", self.contracts) diff --git a/dao/test_moonstream.py b/dao/test_moonstream.py index 7af8a9b..f9afbc6 100644 --- a/dao/test_moonstream.py +++ b/dao/test_moonstream.py @@ -5,7 +5,7 @@ import brownie from . import ERC20Facet, ERC20Initializer from .core import ZERO_ADDRESS, facet_cut -from .test_core import MoonstreamDAOTestCase, MoonstreamDAOFullTestCase +from .test_core import MoonstreamDAOTestCase, MoonstreamTokenTestCase class TestDeployment(MoonstreamDAOTestCase): @@ -118,7 +118,7 @@ class TestRemoveFacet(MoonstreamDAOTestCase): symbol = diamond_erc20.symbol() -class TestERC20(MoonstreamDAOFullTestCase): +class TestERC20(MoonstreamTokenTestCase): def test_mint_fails_if_not_controller(self): diamond_address = self.contracts["Diamond"] diamond = ERC20Facet.ERC20Facet(diamond_address) diff --git a/dao/test_terminus.py b/dao/test_terminus.py index aaa2f39..4fa905f 100644 --- a/dao/test_terminus.py +++ b/dao/test_terminus.py @@ -1,11 +1,10 @@ import unittest from brownie import accounts -import brownie from . import TerminusFacet, TerminusInitializer -from .core import ZERO_ADDRESS, facet_cut -from .test_core import MoonstreamDAOTestCase, MoonstreamDAOFullTestCase +from .core import facet_cut +from .test_core import MoonstreamDAOTestCase, TerminusTestCase class TestDeployment(MoonstreamDAOTestCase): @@ -32,5 +31,19 @@ class TestDeployment(MoonstreamDAOTestCase): self.assertEqual(controller, accounts[0].address) +class TestPoolCreation(TerminusTestCase): + def test_create_pool(self): + diamond_address = self.contracts["Diamond"] + diamond_terminus = TerminusFacet.TerminusFacet(diamond_address) + + initial_total_pools = diamond_terminus.total_pools() + diamond_terminus.create_pool({"from": accounts[1]}) + final_total_pools = diamond_terminus.total_pools() + self.assertEqual(final_total_pools, initial_total_pools + 1) + + pool_controller = diamond_terminus.terminus_pool_controller(final_total_pools) + self.assertEqual(pool_controller, accounts[1].address) + + if __name__ == "__main__": unittest.main() From aeb80d78571ca3b0b74922e13a34b67b0626f036 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Sun, 19 Dec 2021 04:11:18 -0800 Subject: [PATCH 09/18] Moonstream token is platform token, not governance token. Governance will be handled by Terminus pool! --- checklists/moonstream-deploy-mumbai-20211218-1633.md | 4 ++-- checklists/templates/moonstream-deploy.md | 4 ++-- contracts/moonstream/ERC20Facet.sol | 2 +- contracts/moonstream/ERC20Initializer.sol | 2 +- contracts/terminus/TerminusFacet.sol | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/checklists/moonstream-deploy-mumbai-20211218-1633.md b/checklists/moonstream-deploy-mumbai-20211218-1633.md index 26c9401..c637b98 100644 --- a/checklists/moonstream-deploy-mumbai-20211218-1633.md +++ b/checklists/moonstream-deploy-mumbai-20211218-1633.md @@ -1,6 +1,6 @@ -# Deploy the Moonstream governance token +# Deploy the Moonstream platform token -The Moonstream DAO governance token is deployed as an EIP2535 Diamond proxy contract with an ERC20 +The Moonstream DAO platform token is deployed as an EIP2535 Diamond proxy contract with an ERC20 facet attached to it. This checklist describes how to deploy the token. diff --git a/checklists/templates/moonstream-deploy.md b/checklists/templates/moonstream-deploy.md index c815d99..a41dbc7 100644 --- a/checklists/templates/moonstream-deploy.md +++ b/checklists/templates/moonstream-deploy.md @@ -1,6 +1,6 @@ -# Deploy the Moonstream governance token +# Deploy the Moonstream platform token -The Moonstream DAO governance token is deployed as an EIP2535 Diamond proxy contract with an ERC20 +The Moonstream DAO platform token is deployed as an EIP2535 Diamond proxy contract with an ERC20 facet attached to it. This checklist describes how to deploy the token. diff --git a/contracts/moonstream/ERC20Facet.sol b/contracts/moonstream/ERC20Facet.sol index 1e7f8d9..3651559 100644 --- a/contracts/moonstream/ERC20Facet.sol +++ b/contracts/moonstream/ERC20Facet.sol @@ -4,7 +4,7 @@ * Authors: Moonstream Engineering (engineering@moonstream.to) * GitHub: https://github.com/bugout-dev/dao * - * This is an implementation of the ERC20 governance token for the Moonstream DAO. + * This is an implementation of the ERC20 platform token for the Moonstream DAO. */ pragma solidity ^0.8.0; diff --git a/contracts/moonstream/ERC20Initializer.sol b/contracts/moonstream/ERC20Initializer.sol index 9c77148..3c5a185 100644 --- a/contracts/moonstream/ERC20Initializer.sol +++ b/contracts/moonstream/ERC20Initializer.sol @@ -4,7 +4,7 @@ * Authors: Moonstream Engineering (engineering@moonstream.to) * GitHub: https://github.com/bugout-dev/dao * - * Initializer for Moonstream DAO governance token. Used when mounting a new ERC20Facet onto its + * Initializer for Moonstream DAO platform token. Used when mounting a new ERC20Facet onto its * diamond proxy. */ diff --git a/contracts/terminus/TerminusFacet.sol b/contracts/terminus/TerminusFacet.sol index d004d95..3dd01c8 100644 --- a/contracts/terminus/TerminusFacet.sol +++ b/contracts/terminus/TerminusFacet.sol @@ -4,7 +4,7 @@ * Authors: Moonstream Engineering (engineering@moonstream.to) * GitHub: https://github.com/bugout-dev/dao * - * This is an implementation of the ERC20 governance token for the Moonstream DAO. + * This is an implementation of the Terminus decentralized authorization contract. */ pragma solidity ^0.8.0; From 44aa774e5c923b927ee2ff85b50c8510d5984df6 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Sun, 19 Dec 2021 04:26:35 -0800 Subject: [PATCH 10/18] TerminusTestCase now also deploys Moonstream token --- dao/test_core.py | 12 +++++++----- dao/test_moonstream.py | 6 +++--- dao/test_terminus.py | 6 +++--- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/dao/test_core.py b/dao/test_core.py index 6d6c925..7f95891 100644 --- a/dao/test_core.py +++ b/dao/test_core.py @@ -9,7 +9,7 @@ from . import TerminusFacet from . import TerminusInitializer -class MoonstreamDAOTestCase(unittest.TestCase): +class MoonstreamDAOSingleContractTestCase(unittest.TestCase): @classmethod def setUpClass(cls) -> None: try: @@ -19,7 +19,7 @@ class MoonstreamDAOTestCase(unittest.TestCase): cls.contracts = gogogo(accounts[0], {"from": accounts[0]}) -class MoonstreamTokenTestCase(MoonstreamDAOTestCase): +class MoonstreamTokenTestCase(MoonstreamDAOSingleContractTestCase): @classmethod def setUpClass(cls) -> None: super().setUpClass() @@ -45,11 +45,13 @@ class MoonstreamTokenTestCase(MoonstreamDAOTestCase): cls.erc20_facet = erc20_facet.address -class TerminusTestCase(MoonstreamDAOTestCase): +class TerminusTestCase(MoonstreamDAOSingleContractTestCase): @classmethod def setUpClass(cls) -> None: super().setUpClass() + cls.terminus_contracts = gogogo(accounts[0], {"from": accounts[0]}) + # Deploy Terminus initializer = TerminusInitializer.TerminusInitializer(None) initializer.deploy({"from": accounts[0]}) @@ -57,7 +59,7 @@ class TerminusTestCase(MoonstreamDAOTestCase): terminus_facet = TerminusFacet.TerminusFacet(None) terminus_facet.deploy({"from": accounts[0]}) - diamond_address = cls.contracts["Diamond"] + diamond_address = cls.terminus_contracts["Diamond"] facet_cut( diamond_address, "TerminusFacet", @@ -71,7 +73,7 @@ class TerminusTestCase(MoonstreamDAOTestCase): cls.terminus_facet = terminus_facet.address -class TestCoreDeployment(MoonstreamDAOTestCase): +class TestCoreDeployment(MoonstreamDAOSingleContractTestCase): def test_gogogo(self): self.assertIn("DiamondCutFacet", self.contracts) self.assertIn("Diamond", self.contracts) diff --git a/dao/test_moonstream.py b/dao/test_moonstream.py index f9afbc6..fb0d6f4 100644 --- a/dao/test_moonstream.py +++ b/dao/test_moonstream.py @@ -5,10 +5,10 @@ import brownie from . import ERC20Facet, ERC20Initializer from .core import ZERO_ADDRESS, facet_cut -from .test_core import MoonstreamDAOTestCase, MoonstreamTokenTestCase +from .test_core import MoonstreamDAOSingleContractTestCase, MoonstreamTokenTestCase -class TestDeployment(MoonstreamDAOTestCase): +class TestDeployment(MoonstreamDAOSingleContractTestCase): def test_add_and_replace(self): initializer = ERC20Initializer.ERC20Initializer(None) initializer.deploy({"from": accounts[0]}) @@ -72,7 +72,7 @@ class TestDeployment(MoonstreamDAOTestCase): self.assertEqual(symbol, expected_symbol) -class TestRemoveFacet(MoonstreamDAOTestCase): +class TestRemoveFacet(MoonstreamDAOSingleContractTestCase): def test_remove_facet(self): initializer = ERC20Initializer.ERC20Initializer(None) initializer.deploy({"from": accounts[0]}) diff --git a/dao/test_terminus.py b/dao/test_terminus.py index 4fa905f..6007151 100644 --- a/dao/test_terminus.py +++ b/dao/test_terminus.py @@ -4,10 +4,10 @@ from brownie import accounts from . import TerminusFacet, TerminusInitializer from .core import facet_cut -from .test_core import MoonstreamDAOTestCase, TerminusTestCase +from .test_core import MoonstreamDAOSingleContractTestCase, TerminusTestCase -class TestDeployment(MoonstreamDAOTestCase): +class TestDeployment(MoonstreamDAOSingleContractTestCase): def test_add_and_replace(self): initializer = TerminusInitializer.TerminusInitializer(None) initializer.deploy({"from": accounts[0]}) @@ -33,7 +33,7 @@ class TestDeployment(MoonstreamDAOTestCase): class TestPoolCreation(TerminusTestCase): def test_create_pool(self): - diamond_address = self.contracts["Diamond"] + diamond_address = self.terminus_contracts["Diamond"] diamond_terminus = TerminusFacet.TerminusFacet(diamond_address) initial_total_pools = diamond_terminus.total_pools() From 96829f26bd6962971c25dfc5b81c52927dc91701 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Sun, 19 Dec 2021 04:47:24 -0800 Subject: [PATCH 11/18] Added payment token functionality to TerminusFacet --- contracts/terminus/LibTerminus.sol | 4 +- contracts/terminus/TerminusFacet.sol | 39 +++++++++++++++++- dao/TerminusFacet.py | 59 ++++++++++++++++++++++++---- setup.py | 2 +- 4 files changed, 92 insertions(+), 12 deletions(-) diff --git a/contracts/terminus/LibTerminus.sol b/contracts/terminus/LibTerminus.sol index f468a4d..7862522 100644 --- a/contracts/terminus/LibTerminus.sol +++ b/contracts/terminus/LibTerminus.sol @@ -23,6 +23,8 @@ library LibTerminus { address controller; bool isTerminusActive; uint256 currentPoolID; + address paymentToken; + uint256 poolBasePrice; // Terminus pools mapping(uint256 => address) poolController; mapping(uint256 => bool) poolActive; @@ -79,7 +81,7 @@ library LibTerminus { emit PoolControlTransferred(poolID, previousController, newController); } - function createPool() internal returns (uint256) { + function createSimplePool() internal returns (uint256) { TerminusStorage storage ts = terminusStorage(); uint256 poolID = ts.currentPoolID + 1; setPoolController(poolID, msg.sender); diff --git a/contracts/terminus/TerminusFacet.sol b/contracts/terminus/TerminusFacet.sol index 3dd01c8..eee9dc8 100644 --- a/contracts/terminus/TerminusFacet.sol +++ b/contracts/terminus/TerminusFacet.sol @@ -9,6 +9,7 @@ pragma solidity ^0.8.0; +import "@openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; import "./ERC1155WithTerminusStorage.sol"; import "./LibTerminus.sol"; import "../diamond/libraries/LibDiamond.sol"; @@ -20,6 +21,27 @@ contract TerminusFacet is ERC1155WithTerminusStorage { return LibTerminus.terminusStorage().controller; } + function paymentToken() external view returns (address) { + return LibTerminus.terminusStorage().paymentToken; + } + + function setPaymentToken(address newPaymentToken) external { + LibTerminus.enforceIsController(); + LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage(); + ts.paymentToken = newPaymentToken; + } + + function _paymentTokenContract() internal view returns (IERC20) { + address paymentTokenAddress = LibTerminus + .terminusStorage() + .paymentToken; + require( + paymentTokenAddress != address(0), + "TerminusFacet: Payment token has not been set" + ); + return IERC20(paymentTokenAddress); + } + function setURI(uint256 poolID, string memory poolURI) external { LibTerminus.enforcePoolIsController(poolID, _msgSender()); LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage(); @@ -30,8 +52,21 @@ contract TerminusFacet is ERC1155WithTerminusStorage { return LibTerminus.terminusStorage().currentPoolID; } - function createPool() external returns (uint256) { - return LibTerminus.createPool(); + function createSimplePool() external returns (uint256) { + LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage(); + uint256 requiredPayment = ts.poolBasePrice; + IERC20 paymentTokenContract = _paymentTokenContract(); + require( + paymentTokenContract.allowance(_msgSender(), address(this)) >= + requiredPayment, + "TerminusFacet: createSimplePool -- Insufficient allowance on payment token" + ); + paymentTokenContract.transferFrom( + msg.sender, + address(this), + requiredPayment + ); + return LibTerminus.createSimplePool(); } function terminusPoolController(uint256 poolID) diff --git a/dao/TerminusFacet.py b/dao/TerminusFacet.py index 0c0e94b..8eb56fa 100644 --- a/dao/TerminusFacet.py +++ b/dao/TerminusFacet.py @@ -1,5 +1,5 @@ # Code generated by moonworm : https://github.com/bugout-dev/moonworm -# Moonworm version : 0.1.8 +# Moonworm version : 0.1.9 import argparse import json @@ -98,9 +98,9 @@ class TerminusFacet: self.assert_contract_is_instantiated() return self.contract.balanceOfBatch.call(accounts, ids) - def create_pool(self, transaction_config) -> Any: + def create_simple_pool(self, transaction_config) -> Any: self.assert_contract_is_instantiated() - return self.contract.createPool(transaction_config) + return self.contract.createSimplePool(transaction_config) def is_approved_for_all( self, account: ChecksumAddress, operator: ChecksumAddress @@ -130,6 +130,10 @@ class TerminusFacet: self.assert_contract_is_instantiated() return self.contract.mintBatch(to, pool_i_ds, amounts, data, transaction_config) + def payment_token(self) -> Any: + self.assert_contract_is_instantiated() + return self.contract.paymentToken.call() + def safe_batch_transfer_from( self, from_: ChecksumAddress, @@ -164,6 +168,12 @@ class TerminusFacet: self.assert_contract_is_instantiated() return self.contract.setApprovalForAll(operator, approved, transaction_config) + def set_payment_token( + self, new_payment_token: ChecksumAddress, transaction_config + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.setPaymentToken(new_payment_token, transaction_config) + def set_uri(self, pool_id: int, pool_uri: str, transaction_config) -> Any: self.assert_contract_is_instantiated() return self.contract.setURI(pool_id, pool_uri, transaction_config) @@ -196,6 +206,8 @@ def get_transaction_config(args: argparse.Namespace) -> Dict[str, Any]: transaction_config["gas_price"] = args.gas_price if args.confirmations is not None: transaction_config["required_confs"] = args.confirmations + if args.nonce is not None: + transaction_config["nonce"] = args.nonce return transaction_config @@ -225,6 +237,9 @@ def add_default_arguments(parser: argparse.ArgumentParser, transact: bool) -> No default=None, help="Number of confirmations to await before considering a transaction completed", ) + parser.add_argument( + "--nonce", type=int, default=None, help="Nonce for the transaction (optional)" + ) def handle_deploy(args: argparse.Namespace) -> None: @@ -249,11 +264,11 @@ def handle_balance_of_batch(args: argparse.Namespace) -> None: print(result) -def handle_create_pool(args: argparse.Namespace) -> None: +def handle_create_simple_pool(args: argparse.Namespace) -> None: network.connect(args.network) contract = TerminusFacet(args.address) transaction_config = get_transaction_config(args) - result = contract.create_pool(transaction_config=transaction_config) + result = contract.create_simple_pool(transaction_config=transaction_config) print(result) @@ -292,6 +307,13 @@ def handle_mint_batch(args: argparse.Namespace) -> None: print(result) +def handle_payment_token(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = TerminusFacet(args.address) + result = contract.payment_token() + print(result) + + def handle_safe_batch_transfer_from(args: argparse.Namespace) -> None: network.connect(args.network) contract = TerminusFacet(args.address) @@ -334,6 +356,16 @@ def handle_set_approval_for_all(args: argparse.Namespace) -> None: print(result) +def handle_set_payment_token(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = TerminusFacet(args.address) + transaction_config = get_transaction_config(args) + result = contract.set_payment_token( + new_payment_token=args.new_payment_token, transaction_config=transaction_config + ) + print(result) + + def handle_set_uri(args: argparse.Namespace) -> None: network.connect(args.network) contract = TerminusFacet(args.address) @@ -408,9 +440,9 @@ def generate_cli() -> argparse.ArgumentParser: ) balance_of_batch_parser.set_defaults(func=handle_balance_of_batch) - create_pool_parser = subcommands.add_parser("create-pool") - add_default_arguments(create_pool_parser, True) - create_pool_parser.set_defaults(func=handle_create_pool) + create_simple_pool_parser = subcommands.add_parser("create-simple-pool") + add_default_arguments(create_simple_pool_parser, True) + create_simple_pool_parser.set_defaults(func=handle_create_simple_pool) is_approved_for_all_parser = subcommands.add_parser("is-approved-for-all") add_default_arguments(is_approved_for_all_parser, False) @@ -446,6 +478,10 @@ def generate_cli() -> argparse.ArgumentParser: ) mint_batch_parser.set_defaults(func=handle_mint_batch) + payment_token_parser = subcommands.add_parser("payment-token") + add_default_arguments(payment_token_parser, False) + payment_token_parser.set_defaults(func=handle_payment_token) + safe_batch_transfer_from_parser = subcommands.add_parser("safe-batch-transfer-from") add_default_arguments(safe_batch_transfer_from_parser, True) safe_batch_transfer_from_parser.add_argument( @@ -492,6 +528,13 @@ def generate_cli() -> argparse.ArgumentParser: ) set_approval_for_all_parser.set_defaults(func=handle_set_approval_for_all) + set_payment_token_parser = subcommands.add_parser("set-payment-token") + add_default_arguments(set_payment_token_parser, True) + set_payment_token_parser.add_argument( + "--new-payment-token", required=True, help="Type: address" + ) + set_payment_token_parser.set_defaults(func=handle_set_payment_token) + set_uri_parser = subcommands.add_parser("set-uri") add_default_arguments(set_uri_parser, True) set_uri_parser.add_argument( diff --git a/setup.py b/setup.py index 5214b31..66e1c61 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ setup( extras_require={ "dev": [ "black", - "moonworm", + "moonworm >= 0.1.9", ], "distribute": ["setuptools", "twine", "wheel"], }, From 6ad179f452aa3aee0262d48e77fa54e66e229c35 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Sun, 19 Dec 2021 05:07:21 -0800 Subject: [PATCH 12/18] Added payment functionality to Terminus --- contracts/terminus/TerminusFacet.sol | 20 +++++++ dao/TerminusFacet.py | 64 ++++++++++++++++++++++ dao/test_core.py | 2 +- dao/test_terminus.py | 80 ++++++++++++++++++++++++++-- 4 files changed, 160 insertions(+), 6 deletions(-) diff --git a/contracts/terminus/TerminusFacet.sol b/contracts/terminus/TerminusFacet.sol index eee9dc8..6cde104 100644 --- a/contracts/terminus/TerminusFacet.sol +++ b/contracts/terminus/TerminusFacet.sol @@ -31,6 +31,16 @@ contract TerminusFacet is ERC1155WithTerminusStorage { ts.paymentToken = newPaymentToken; } + function poolBasePrice() external view returns (uint256) { + return LibTerminus.terminusStorage().poolBasePrice; + } + + function setPoolBasePrice(uint256 newBasePrice) external { + LibTerminus.enforceIsController(); + LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage(); + ts.poolBasePrice = newBasePrice; + } + function _paymentTokenContract() internal view returns (IERC20) { address paymentTokenAddress = LibTerminus .terminusStorage() @@ -42,6 +52,16 @@ contract TerminusFacet is ERC1155WithTerminusStorage { return IERC20(paymentTokenAddress); } + function withdrawPayments(address toAddress, uint256 amount) external { + LibTerminus.enforceIsController(); + require( + _msgSender() == toAddress, + "TerminusFacet: withdrawPayments -- Controller can only withdraw to self" + ); + IERC20 paymentTokenContract = _paymentTokenContract(); + paymentTokenContract.transfer(toAddress, amount); + } + function setURI(uint256 poolID, string memory poolURI) external { LibTerminus.enforcePoolIsController(poolID, _msgSender()); LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage(); diff --git a/dao/TerminusFacet.py b/dao/TerminusFacet.py index 8eb56fa..9efb786 100644 --- a/dao/TerminusFacet.py +++ b/dao/TerminusFacet.py @@ -134,6 +134,10 @@ class TerminusFacet: self.assert_contract_is_instantiated() return self.contract.paymentToken.call() + def pool_base_price(self) -> Any: + self.assert_contract_is_instantiated() + return self.contract.poolBasePrice.call() + def safe_batch_transfer_from( self, from_: ChecksumAddress, @@ -174,6 +178,10 @@ class TerminusFacet: self.assert_contract_is_instantiated() return self.contract.setPaymentToken(new_payment_token, transaction_config) + def set_pool_base_price(self, new_base_price: int, transaction_config) -> Any: + self.assert_contract_is_instantiated() + return self.contract.setPoolBasePrice(new_base_price, transaction_config) + def set_uri(self, pool_id: int, pool_uri: str, transaction_config) -> Any: self.assert_contract_is_instantiated() return self.contract.setURI(pool_id, pool_uri, transaction_config) @@ -198,6 +206,12 @@ class TerminusFacet: self.assert_contract_is_instantiated() return self.contract.uri.call(pool_id) + def withdraw_payments( + self, to_address: ChecksumAddress, amount: int, transaction_config + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.withdrawPayments(to_address, amount, transaction_config) + def get_transaction_config(args: argparse.Namespace) -> Dict[str, Any]: signer = network.accounts.load(args.sender, args.password) @@ -314,6 +328,13 @@ def handle_payment_token(args: argparse.Namespace) -> None: print(result) +def handle_pool_base_price(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = TerminusFacet(args.address) + result = contract.pool_base_price() + print(result) + + def handle_safe_batch_transfer_from(args: argparse.Namespace) -> None: network.connect(args.network) contract = TerminusFacet(args.address) @@ -366,6 +387,16 @@ def handle_set_payment_token(args: argparse.Namespace) -> None: print(result) +def handle_set_pool_base_price(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = TerminusFacet(args.address) + transaction_config = get_transaction_config(args) + result = contract.set_pool_base_price( + new_base_price=args.new_base_price, transaction_config=transaction_config + ) + print(result) + + def handle_set_uri(args: argparse.Namespace) -> None: network.connect(args.network) contract = TerminusFacet(args.address) @@ -413,6 +444,18 @@ def handle_uri(args: argparse.Namespace) -> None: print(result) +def handle_withdraw_payments(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = TerminusFacet(args.address) + transaction_config = get_transaction_config(args) + result = contract.withdraw_payments( + to_address=args.to_address, + amount=args.amount, + transaction_config=transaction_config, + ) + print(result) + + def generate_cli() -> argparse.ArgumentParser: parser = argparse.ArgumentParser(description="CLI for TerminusFacet") parser.set_defaults(func=lambda _: parser.print_help()) @@ -482,6 +525,10 @@ def generate_cli() -> argparse.ArgumentParser: add_default_arguments(payment_token_parser, False) payment_token_parser.set_defaults(func=handle_payment_token) + pool_base_price_parser = subcommands.add_parser("pool-base-price") + add_default_arguments(pool_base_price_parser, False) + pool_base_price_parser.set_defaults(func=handle_pool_base_price) + safe_batch_transfer_from_parser = subcommands.add_parser("safe-batch-transfer-from") add_default_arguments(safe_batch_transfer_from_parser, True) safe_batch_transfer_from_parser.add_argument( @@ -535,6 +582,13 @@ def generate_cli() -> argparse.ArgumentParser: ) set_payment_token_parser.set_defaults(func=handle_set_payment_token) + set_pool_base_price_parser = subcommands.add_parser("set-pool-base-price") + add_default_arguments(set_pool_base_price_parser, True) + set_pool_base_price_parser.add_argument( + "--new-base-price", required=True, help="Type: uint256", type=int + ) + set_pool_base_price_parser.set_defaults(func=handle_set_pool_base_price) + set_uri_parser = subcommands.add_parser("set-uri") add_default_arguments(set_uri_parser, True) set_uri_parser.add_argument( @@ -572,6 +626,16 @@ def generate_cli() -> argparse.ArgumentParser: uri_parser.add_argument("--pool-id", required=True, help="Type: uint256", type=int) uri_parser.set_defaults(func=handle_uri) + withdraw_payments_parser = subcommands.add_parser("withdraw-payments") + add_default_arguments(withdraw_payments_parser, True) + withdraw_payments_parser.add_argument( + "--to-address", required=True, help="Type: address" + ) + withdraw_payments_parser.add_argument( + "--amount", required=True, help="Type: uint256", type=int + ) + withdraw_payments_parser.set_defaults(func=handle_withdraw_payments) + return parser diff --git a/dao/test_core.py b/dao/test_core.py index 7f95891..0c95231 100644 --- a/dao/test_core.py +++ b/dao/test_core.py @@ -45,7 +45,7 @@ class MoonstreamTokenTestCase(MoonstreamDAOSingleContractTestCase): cls.erc20_facet = erc20_facet.address -class TerminusTestCase(MoonstreamDAOSingleContractTestCase): +class TerminusTestCase(MoonstreamTokenTestCase): @classmethod def setUpClass(cls) -> None: super().setUpClass() diff --git a/dao/test_terminus.py b/dao/test_terminus.py index 6007151..3f8d927 100644 --- a/dao/test_terminus.py +++ b/dao/test_terminus.py @@ -2,7 +2,7 @@ import unittest from brownie import accounts -from . import TerminusFacet, TerminusInitializer +from . import ERC20Facet, TerminusFacet, TerminusInitializer from .core import facet_cut from .test_core import MoonstreamDAOSingleContractTestCase, TerminusTestCase @@ -32,15 +32,85 @@ class TestDeployment(MoonstreamDAOSingleContractTestCase): class TestPoolCreation(TerminusTestCase): - def test_create_pool(self): - diamond_address = self.terminus_contracts["Diamond"] - diamond_terminus = TerminusFacet.TerminusFacet(diamond_address) + def test_create_simple_pool(self): + moonstream_diamond_address = self.contracts["Diamond"] + diamond_moonstream = ERC20Facet.ERC20Facet(moonstream_diamond_address) + + terminus_diamond_address = self.terminus_contracts["Diamond"] + diamond_terminus = TerminusFacet.TerminusFacet(terminus_diamond_address) + + diamond_terminus.set_payment_token( + moonstream_diamond_address, {"from": accounts[0]} + ) + payment_token = diamond_terminus.payment_token() + self.assertEqual(payment_token, moonstream_diamond_address) + + diamond_terminus.set_pool_base_price(1000, {"from": accounts[0]}) + pool_base_price = diamond_terminus.pool_base_price() + self.assertEqual(pool_base_price, 1000) + + diamond_moonstream.mint(accounts[1], 1000, {"from": accounts[0]}) + initial_payer_balance = diamond_moonstream.balance_of(accounts[1].address) + initial_terminus_balance = diamond_moonstream.balance_of( + terminus_diamond_address + ) + initial_controller_balance = diamond_moonstream.balance_of(accounts[0].address) + + diamond_moonstream.approve( + terminus_diamond_address, 1000, {"from": accounts[1]} + ) initial_total_pools = diamond_terminus.total_pools() - diamond_terminus.create_pool({"from": accounts[1]}) + + diamond_terminus.create_simple_pool({"from": accounts[1]}) + final_total_pools = diamond_terminus.total_pools() self.assertEqual(final_total_pools, initial_total_pools + 1) + final_payer_balance = diamond_moonstream.balance_of(accounts[1].address) + intermediate_terminus_balance = diamond_moonstream.balance_of( + terminus_diamond_address + ) + intermediate_controller_balance = diamond_moonstream.balance_of( + accounts[0].address + ) + self.assertEqual(final_payer_balance, initial_payer_balance - 1000) + self.assertEqual(intermediate_terminus_balance, initial_terminus_balance + 1000) + self.assertEqual(intermediate_controller_balance, initial_controller_balance) + + with self.assertRaises(Exception): + diamond_terminus.withdraw_payments( + accounts[1].address, 1000, {"from": accounts[1]} + ) + + with self.assertRaises(Exception): + diamond_terminus.withdraw_payments( + accounts[0].address, 1000, {"from": accounts[1]} + ) + + with self.assertRaises(Exception): + diamond_terminus.withdraw_payments( + accounts[1].address, 1000, {"from": accounts[0]} + ) + + diamond_terminus.withdraw_payments( + accounts[0].address, 1000, {"from": accounts[0]} + ) + + final_terminus_balance = diamond_moonstream.balance_of(terminus_diamond_address) + final_controller_balance = diamond_moonstream.balance_of(accounts[0].address) + self.assertEqual(final_terminus_balance, intermediate_terminus_balance - 1000) + self.assertEqual( + final_controller_balance, intermediate_controller_balance + 1000 + ) + + with self.assertRaises(Exception): + diamond_terminus.withdraw_payments( + accounts[0].address, + final_terminus_balance + 1000, + {"from": accounts[0]}, + ) + pool_controller = diamond_terminus.terminus_pool_controller(final_total_pools) self.assertEqual(pool_controller, accounts[1].address) From d42e84d43ee6f0039d964291df4b2eb723f4741a Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Tue, 21 Dec 2021 11:25:06 -0800 Subject: [PATCH 13/18] Deployed new ERC20Facet with "moonstreamController" method --- .../moonstream-update-mumbai-20211221-1912.md | 86 +++++++++++++++ checklists/templates/moonstream-update.md | 102 ++++++++++++++++++ 2 files changed, 188 insertions(+) create mode 100644 checklists/moonstream-update-mumbai-20211221-1912.md create mode 100644 checklists/templates/moonstream-update.md diff --git a/checklists/moonstream-update-mumbai-20211221-1912.md b/checklists/moonstream-update-mumbai-20211221-1912.md new file mode 100644 index 0000000..f5fdc01 --- /dev/null +++ b/checklists/moonstream-update-mumbai-20211221-1912.md @@ -0,0 +1,86 @@ +# Update ERC20Facet on the Moonstream platform token + +The Moonstream DAO platform token is deployed as an EIP2535 Diamond proxy contract with an ERC20 +facet attached to it. + +This checklist describes how to deploy the token. + +## Deployed addresses + +You will modify this section as you go through the checklist + +### `ERC20Facet` address + +``` +export ERC20FACET_ADDRESS="0x973359bC17de4B2A84Fe3151B5B857f12cf423CB" +``` + +## Environment variables + +- [ ] `export DAO_NETWORK=` +- [ ] `export DAO_OWNER=` +- [ ] `export DAO_OWNER_ADDRESS=$(jq -r .address $DAO_OWNER)` +- [ ] `export GAS_PRICE=" gwei"` +- [ ] `export CONFIRMATIONS=` +- [ ] `export MOONSTREAM_ADDRESSES=` +- [ ] `export MOONSTREAM_DIAMOND="$(jq -r .Diamond $MOONSTREAM_ADDRESSES)"` + +## Deploy `ERC20Facet` + +- [x] Deploy `ERC20Facet` contract + +```bash +dao moonstream deploy \ + --network $DAO_NETWORK \ + --sender $DAO_OWNER \ + --gas-price "$GAS_PRICE" \ + --confirmations $CONFIRMATIONS +``` + +- [x] Export address of deployed contract as `export ERC20FACET_ADDRESS=
` + +- [x] Store address of deployed contract under `Deployed addresses / ERC20Facet address` above + +- [x] Remove old `ERC20Facet` from diamond. + +This may require you to check out a different commit and compile at that commit. + +```bash +dao core facet-cut \ + --address $MOONSTREAM_DIAMOND \ + --network $DAO_NETWORK \ + --sender $DAO_OWNER \ + --gas-price "$GAS_PRICE" \ + --confirmations $CONFIRMATIONS \ + --facet-name ERC20Facet \ + --facet-address $ERC20FACET_ADDRESS \ + --action remove +``` + +- [x] Attach `ERC20Facet` to diamond. + +Check out correct commit and do a `brownie compile`. + +```bash +dao core facet-cut \ + --address $MOONSTREAM_DIAMOND \ + --network $DAO_NETWORK \ + --sender $DAO_OWNER \ + --gas-price "$GAS_PRICE" \ + --confirmations $CONFIRMATIONS \ + --facet-name ERC20Facet \ + --facet-address $ERC20FACET_ADDRESS \ + --action add +``` + +- [x] Check the ERC20 name of the diamond contract: `dao moonstream name --network $DAO_NETWORK --address $MOONSTREAM_DIAMOND` + +- [x] Name is `Moonstream DAO` + +- [x] Check the ERC20 symbol of the diamond contract: `dao moonstream symbol --network $DAO_NETWORK --address $MOONSTREAM_DIAMOND` + +- [x] Symbol is `MNSTR` + +- [x] Check the controller of the diamond contract: `dao moonstream moonstream-controller --network $DAO_NETWORK --address $MOONSTREAM_DIAMOND` + +- [x] Controller is `$DAO_OWNER_ADDRESS` diff --git a/checklists/templates/moonstream-update.md b/checklists/templates/moonstream-update.md new file mode 100644 index 0000000..6b4e588 --- /dev/null +++ b/checklists/templates/moonstream-update.md @@ -0,0 +1,102 @@ +# Update ERC20Facet on the Moonstream platform token + +The Moonstream DAO platform token is deployed as an EIP2535 Diamond proxy contract with an ERC20 +facet attached to it. + +This checklist describes how to deploy the token. + +## Deployed addresses + +You will modify this section as you go through the checklist + +### `ERC20Initializer` address + +``` +export ERC20INITIALIZER_ADDRESS="" +``` + +### `ERC20Facet` address + +``` +export ERC20FACET_ADDRESS="" +``` + +## Environment variables + +- [ ] `export DAO_NETWORK=` +- [ ] `export DAO_OWNER=` +- [ ] `export DAO_OWNER_ADDRESS=$(jq -r .address $DAO_OWNER)` +- [ ] `export GAS_PRICE=" gwei"` +- [ ] `export CONFIRMATIONS=` +- [ ] `export MOONSTREAM_ADDRESSES=` +- [ ] `export MOONSTREAM_TOTAL_SUPPLY=` +- [ ] `export MOONSTREAM_DIAMOND=""` + +## (Optional) Deploy `ERC20Initializer` + +- [ ] Deploy `ERC20Initializer` contract + +```bash +dao moonstream-initializer deploy \ + --network $DAO_NETWORK \ + --sender $DAO_OWNER \ + --gas-price "$GAS_PRICE" \ + --confirmations $CONFIRMATIONS +``` + +- [ ] Export address of deployed contract as `export ERC20INITIALIZER_ADDRESS=
` + +- [ ] Store address of deployed contract under `Deployed addresses / ERC20Initializer address` above + +## Deploy `ERC20Facet` + +- [ ] Deploy `ERC20Facet` contract + +```bash +dao moonstream deploy \ + --network $DAO_NETWORK \ + --sender $DAO_OWNER \ + --gas-price "$GAS_PRICE" \ + --confirmations $CONFIRMATIONS +``` + +- [ ] Export address of deployed contract as `export ERC20FACET_ADDRESS=
` + +- [ ] Store address of deployed contract under `Deployed addresses / ERC20Facet address` above + +- [ ] Attach `ERC20Facet` to diamond: + +If initializer was deployed: +```bash +dao core facet-cut \ + --address $MOONSTREAM_DIAMOND \ + --network $DAO_NETWORK \ + --sender $DAO_OWNER \ + --gas-price "$GAS_PRICE" \ + --confirmations $CONFIRMATIONS \ + --facet-name ERC20Facet \ + --facet-address $ERC20FACET_ADDRESS \ + --action add \ + --initializer-address $ERC20INITIALIZER_ADDRESS +``` + +Otherwise: +```bash +dao core facet-cut \ + --address $MOONSTREAM_DIAMOND \ + --network $DAO_NETWORK \ + --sender $DAO_OWNER \ + --gas-price "$GAS_PRICE" \ + --confirmations $CONFIRMATIONS \ + --facet-name ERC20Facet \ + --facet-address $ERC20FACET_ADDRESS \ + --action add +``` + +- [ ] Check the ERC20 name of the diamond contract: `dao moonstream name --network $DAO_NETWORK --address $MOONSTREAM_DIAMOND` + +- [ ] Name is `Moonstream DAO` + +- [ ] Check the ERC20 symbol of the diamond contract: `dao moonstream symbol --network $DAO_NETWORK --address $MOONSTREAM_DIAMOND` + +- [ ] Symbol is `MNSTR` From f5327186afa7c0e0837105eca2f5c603b3b6374f Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Tue, 21 Dec 2021 11:26:24 -0800 Subject: [PATCH 14/18] Forgot to check off items on Mumbai Moonstream update checklist --- .../moonstream-update-mumbai-20211221-1912.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/checklists/moonstream-update-mumbai-20211221-1912.md b/checklists/moonstream-update-mumbai-20211221-1912.md index f5fdc01..3620a1a 100644 --- a/checklists/moonstream-update-mumbai-20211221-1912.md +++ b/checklists/moonstream-update-mumbai-20211221-1912.md @@ -17,13 +17,13 @@ export ERC20FACET_ADDRESS="0x973359bC17de4B2A84Fe3151B5B857f12cf423CB" ## Environment variables -- [ ] `export DAO_NETWORK=` -- [ ] `export DAO_OWNER=` -- [ ] `export DAO_OWNER_ADDRESS=$(jq -r .address $DAO_OWNER)` -- [ ] `export GAS_PRICE=" gwei"` -- [ ] `export CONFIRMATIONS=` -- [ ] `export MOONSTREAM_ADDRESSES=` -- [ ] `export MOONSTREAM_DIAMOND="$(jq -r .Diamond $MOONSTREAM_ADDRESSES)"` +- [x] `export DAO_NETWORK=` +- [x] `export DAO_OWNER=` +- [x] `export DAO_OWNER_ADDRESS=$(jq -r .address $DAO_OWNER)` +- [x] `export GAS_PRICE=" gwei"` +- [x] `export CONFIRMATIONS=` +- [x] `export MOONSTREAM_ADDRESSES=` +- [x] `export MOONSTREAM_DIAMOND="$(jq -r .Diamond $MOONSTREAM_ADDRESSES)"` ## Deploy `ERC20Facet` From aed0d9584cd621cfdb2177bdee9b11485e0ae731 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Tue, 21 Dec 2021 11:48:02 -0800 Subject: [PATCH 15/18] Added poolCapacity to Terminus Removed `poolActive` --- .../terminus/ERC1155WithTerminusStorage.sol | 17 +++++++-- contracts/terminus/LibTerminus.sol | 21 ++--------- contracts/terminus/TerminusFacet.sol | 37 ++++++++++++++----- dao/TerminusFacet.py | 29 +++++++++++++-- dao/test_terminus.py | 5 ++- test.sh | 2 + 6 files changed, 76 insertions(+), 35 deletions(-) diff --git a/contracts/terminus/ERC1155WithTerminusStorage.sol b/contracts/terminus/ERC1155WithTerminusStorage.sol index 143f473..e621541 100644 --- a/contracts/terminus/ERC1155WithTerminusStorage.sol +++ b/contracts/terminus/ERC1155WithTerminusStorage.sol @@ -292,6 +292,11 @@ contract ERC1155WithTerminusStorage is bytes memory data ) internal virtual { require(to != address(0), "ERC1155: mint to the zero address"); + LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage(); + require( + ts.poolSupply[id] + amount <= ts.poolCapacity[id], + "ERC1155WithTerminusStorage: _mint -- Minted tokens would exceed pool capacity" + ); address operator = _msgSender(); @@ -304,7 +309,6 @@ contract ERC1155WithTerminusStorage is data ); - LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage(); ts.poolBalances[id][to] += amount; emit TransferSingle(operator, address(0), to, id, amount); @@ -339,12 +343,19 @@ contract ERC1155WithTerminusStorage is "ERC1155: ids and amounts length mismatch" ); + LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage(); + + for (uint256 i = 0; i < ids.length; i++) { + require( + ts.poolSupply[ids[i]] + amounts[i] <= ts.poolCapacity[ids[i]], + "ERC1155WithTerminusStorage: _mintBatch -- Minted tokens would exceed pool capacity" + ); + } + address operator = _msgSender(); _beforeTokenTransfer(operator, address(0), to, ids, amounts, data); - LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage(); - for (uint256 i = 0; i < ids.length; i++) { ts.poolBalances[ids[i]][to] += amounts[i]; } diff --git a/contracts/terminus/LibTerminus.sol b/contracts/terminus/LibTerminus.sol index 7862522..529b86d 100644 --- a/contracts/terminus/LibTerminus.sol +++ b/contracts/terminus/LibTerminus.sol @@ -27,8 +27,8 @@ library LibTerminus { uint256 poolBasePrice; // Terminus pools mapping(uint256 => address) poolController; - mapping(uint256 => bool) poolActive; mapping(uint256 => string) poolURI; + mapping(uint256 => uint256) poolCapacity; mapping(uint256 => uint256) poolSupply; mapping(uint256 => mapping(address => uint256)) poolBalances; mapping(address => mapping(address => bool)) globalOperatorApprovals; @@ -81,30 +81,15 @@ library LibTerminus { emit PoolControlTransferred(poolID, previousController, newController); } - function createSimplePool() internal returns (uint256) { + function createSimplePool(uint256 _capacity) internal returns (uint256) { TerminusStorage storage ts = terminusStorage(); uint256 poolID = ts.currentPoolID + 1; setPoolController(poolID, msg.sender); + ts.poolCapacity[poolID] = _capacity; ts.currentPoolID++; return poolID; } - function enforceIsActive() internal view { - TerminusStorage storage ts = terminusStorage(); - require( - ts.isTerminusActive, - "LibTerminus: Terminus contract must be active" - ); - } - - function enforcePoolIsActive(uint256 poolID) internal view { - TerminusStorage storage ts = terminusStorage(); - require( - ts.poolActive[poolID], - "LibTerminus: Terminus pool must be active" - ); - } - function enforcePoolIsController(uint256 poolID, address maybeController) internal view diff --git a/contracts/terminus/TerminusFacet.sol b/contracts/terminus/TerminusFacet.sol index 6cde104..7a86d2d 100644 --- a/contracts/terminus/TerminusFacet.sol +++ b/contracts/terminus/TerminusFacet.sol @@ -5,6 +5,15 @@ * GitHub: https://github.com/bugout-dev/dao * * This is an implementation of the Terminus decentralized authorization contract. + * + * Terminus users can create authorization pools. Each authorization pool has the following properties: + * 1. Controller: The address that controls the pool. Initially set to be the address of the pool creator. + * 2. Pool URI: Metadata URI for the authorization pool. + * 3. Pool capacity: The total number of tokens that can be minted in that authorization pool. + * 4. Pool supply: The number of tokens that have actually been minted in that authorization pool. + * 5. Transferable: A boolean value which denotes whether or not tokens from that pool can be transfered + * between addresses. + * 6. Burnable: A boolean value which denotes whether or not tokens from that pool can be burned. */ pragma solidity ^0.8.0; @@ -72,7 +81,23 @@ contract TerminusFacet is ERC1155WithTerminusStorage { return LibTerminus.terminusStorage().currentPoolID; } - function createSimplePool() external returns (uint256) { + function terminusPoolController(uint256 poolID) + external + view + returns (address) + { + return LibTerminus.terminusStorage().poolController[poolID]; + } + + function terminusPoolCapacity(uint256 poolID) + external + view + returns (uint256) + { + return LibTerminus.terminusStorage().poolCapacity[poolID]; + } + + function createSimplePool(uint256 _capacity) external returns (uint256) { LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage(); uint256 requiredPayment = ts.poolBasePrice; IERC20 paymentTokenContract = _paymentTokenContract(); @@ -86,15 +111,7 @@ contract TerminusFacet is ERC1155WithTerminusStorage { address(this), requiredPayment ); - return LibTerminus.createSimplePool(); - } - - function terminusPoolController(uint256 poolID) - external - view - returns (address) - { - return LibTerminus.terminusStorage().poolController[poolID]; + return LibTerminus.createSimplePool(_capacity); } function mint( diff --git a/dao/TerminusFacet.py b/dao/TerminusFacet.py index 9efb786..41f3fad 100644 --- a/dao/TerminusFacet.py +++ b/dao/TerminusFacet.py @@ -98,9 +98,9 @@ class TerminusFacet: self.assert_contract_is_instantiated() return self.contract.balanceOfBatch.call(accounts, ids) - def create_simple_pool(self, transaction_config) -> Any: + def create_simple_pool(self, _capacity: int, transaction_config) -> Any: self.assert_contract_is_instantiated() - return self.contract.createSimplePool(transaction_config) + return self.contract.createSimplePool(_capacity, transaction_config) def is_approved_for_all( self, account: ChecksumAddress, operator: ChecksumAddress @@ -194,6 +194,10 @@ class TerminusFacet: self.assert_contract_is_instantiated() return self.contract.terminusController.call() + def terminus_pool_capacity(self, pool_id: int) -> Any: + self.assert_contract_is_instantiated() + return self.contract.terminusPoolCapacity.call(pool_id) + def terminus_pool_controller(self, pool_id: int) -> Any: self.assert_contract_is_instantiated() return self.contract.terminusPoolController.call(pool_id) @@ -282,7 +286,9 @@ def handle_create_simple_pool(args: argparse.Namespace) -> None: network.connect(args.network) contract = TerminusFacet(args.address) transaction_config = get_transaction_config(args) - result = contract.create_simple_pool(transaction_config=transaction_config) + result = contract.create_simple_pool( + _capacity=args.capacity_arg, transaction_config=transaction_config + ) print(result) @@ -423,6 +429,13 @@ def handle_terminus_controller(args: argparse.Namespace) -> None: print(result) +def handle_terminus_pool_capacity(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = TerminusFacet(args.address) + result = contract.terminus_pool_capacity(pool_id=args.pool_id) + print(result) + + def handle_terminus_pool_controller(args: argparse.Namespace) -> None: network.connect(args.network) contract = TerminusFacet(args.address) @@ -485,6 +498,9 @@ def generate_cli() -> argparse.ArgumentParser: create_simple_pool_parser = subcommands.add_parser("create-simple-pool") add_default_arguments(create_simple_pool_parser, True) + create_simple_pool_parser.add_argument( + "--capacity-arg", required=True, help="Type: uint256", type=int + ) create_simple_pool_parser.set_defaults(func=handle_create_simple_pool) is_approved_for_all_parser = subcommands.add_parser("is-approved-for-all") @@ -610,6 +626,13 @@ def generate_cli() -> argparse.ArgumentParser: add_default_arguments(terminus_controller_parser, False) terminus_controller_parser.set_defaults(func=handle_terminus_controller) + terminus_pool_capacity_parser = subcommands.add_parser("terminus-pool-capacity") + add_default_arguments(terminus_pool_capacity_parser, False) + terminus_pool_capacity_parser.add_argument( + "--pool-id", required=True, help="Type: uint256", type=int + ) + terminus_pool_capacity_parser.set_defaults(func=handle_terminus_pool_capacity) + terminus_pool_controller_parser = subcommands.add_parser("terminus-pool-controller") add_default_arguments(terminus_pool_controller_parser, False) terminus_pool_controller_parser.add_argument( diff --git a/dao/test_terminus.py b/dao/test_terminus.py index 3f8d927..f26a79b 100644 --- a/dao/test_terminus.py +++ b/dao/test_terminus.py @@ -62,7 +62,7 @@ class TestPoolCreation(TerminusTestCase): initial_total_pools = diamond_terminus.total_pools() - diamond_terminus.create_simple_pool({"from": accounts[1]}) + diamond_terminus.create_simple_pool(10, {"from": accounts[1]}) final_total_pools = diamond_terminus.total_pools() self.assertEqual(final_total_pools, initial_total_pools + 1) @@ -114,6 +114,9 @@ class TestPoolCreation(TerminusTestCase): pool_controller = diamond_terminus.terminus_pool_controller(final_total_pools) self.assertEqual(pool_controller, accounts[1].address) + pool_capacity = diamond_terminus.terminus_pool_capacity(final_total_pools) + self.assertEqual(pool_capacity, 10) + if __name__ == "__main__": unittest.main() diff --git a/test.sh b/test.sh index 0a75eeb..03e636c 100755 --- a/test.sh +++ b/test.sh @@ -4,6 +4,8 @@ # You can set up the local copy of `dao` for development using: # pip install -e .[dev] +set -e + usage() { echo "Usage: $0" [TEST_SPEC ...] echo From 973e49526d1d660c62439eee5a737e3764043dda Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Tue, 21 Dec 2021 13:05:40 -0800 Subject: [PATCH 16/18] Added Terminus pool features 0. Capacity 1. Transferable 2. Burnable --- .../terminus/ERC1155WithTerminusStorage.sol | 132 +++++-- contracts/terminus/LibTerminus.sol | 3 +- contracts/terminus/TerminusFacet.sol | 49 ++- dao/TerminusFacet.py | 102 +++++ dao/test_terminus.py | 359 ++++++++++++++++++ 5 files changed, 616 insertions(+), 29 deletions(-) diff --git a/contracts/terminus/ERC1155WithTerminusStorage.sol b/contracts/terminus/ERC1155WithTerminusStorage.sol index e621541..7728299 100644 --- a/contracts/terminus/ERC1155WithTerminusStorage.sol +++ b/contracts/terminus/ERC1155WithTerminusStorage.sol @@ -75,7 +75,7 @@ contract ERC1155WithTerminusStorage is { require( account != address(0), - "ERC1155: balance query for the zero address" + "ERC1155WithTerminusStorage: balance query for the zero address" ); return LibTerminus.terminusStorage().poolBalances[id][account]; } @@ -96,7 +96,7 @@ contract ERC1155WithTerminusStorage is { require( accounts.length == ids.length, - "ERC1155: accounts and ids length mismatch" + "ERC1155WithTerminusStorage: accounts and ids length mismatch" ); uint256[] memory batchBalances = new uint256[](accounts.length); @@ -135,6 +135,18 @@ contract ERC1155WithTerminusStorage is ]; } + function isApprovedForPool(uint256 poolID, address operator) + public + view + returns (bool) + { + LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage(); + if (operator == ts.poolController[poolID]) { + return true; + } + return false; + } + /** * @dev See {IERC1155-safeTransferFrom}. */ @@ -146,8 +158,10 @@ contract ERC1155WithTerminusStorage is bytes memory data ) public virtual override { require( - from == _msgSender() || isApprovedForAll(from, _msgSender()), - "ERC1155: caller is not owner nor approved" + from == _msgSender() || + isApprovedForAll(from, _msgSender()) || + isApprovedForPool(id, _msgSender()), + "ERC1155WithTerminusStorage: caller is not owner nor approved" ); _safeTransferFrom(from, to, id, amount, data); } @@ -164,7 +178,7 @@ contract ERC1155WithTerminusStorage is ) public virtual override { require( from == _msgSender() || isApprovedForAll(from, _msgSender()), - "ERC1155: transfer caller is not owner nor approved" + "ERC1155WithTerminusStorage: transfer caller is not owner nor approved" ); _safeBatchTransferFrom(from, to, ids, amounts, data); } @@ -188,7 +202,15 @@ contract ERC1155WithTerminusStorage is uint256 amount, bytes memory data ) internal virtual { - require(to != address(0), "ERC1155: transfer to the zero address"); + require( + to != address(0), + "ERC1155WithTerminusStorage: transfer to the zero address" + ); + LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage(); + require( + !ts.poolNotTransferable[id], + "ERC1155WithTerminusStorage: _safeTransferFrom -- pool is not transferable" + ); address operator = _msgSender(); @@ -201,12 +223,10 @@ contract ERC1155WithTerminusStorage is data ); - LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage(); - uint256 fromBalance = ts.poolBalances[id][from]; require( fromBalance >= amount, - "ERC1155: insufficient balance for transfer" + "ERC1155WithTerminusStorage: insufficient balance for transfer" ); unchecked { ts.poolBalances[id][from] = fromBalance - amount; @@ -237,9 +257,12 @@ contract ERC1155WithTerminusStorage is ) internal virtual { require( ids.length == amounts.length, - "ERC1155: ids and amounts length mismatch" + "ERC1155WithTerminusStorage: ids and amounts length mismatch" + ); + require( + to != address(0), + "ERC1155WithTerminusStorage: transfer to the zero address" ); - require(to != address(0), "ERC1155: transfer to the zero address"); address operator = _msgSender(); @@ -254,7 +277,7 @@ contract ERC1155WithTerminusStorage is uint256 fromBalance = ts.poolBalances[id][from]; require( fromBalance >= amount, - "ERC1155: insufficient balance for transfer" + "ERC1155WithTerminusStorage: insufficient balance for transfer" ); unchecked { ts.poolBalances[id][from] = fromBalance - amount; @@ -291,7 +314,10 @@ contract ERC1155WithTerminusStorage is uint256 amount, bytes memory data ) internal virtual { - require(to != address(0), "ERC1155: mint to the zero address"); + require( + to != address(0), + "ERC1155WithTerminusStorage: mint to the zero address" + ); LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage(); require( ts.poolSupply[id] + amount <= ts.poolCapacity[id], @@ -309,6 +335,7 @@ contract ERC1155WithTerminusStorage is data ); + ts.poolSupply[id] += amount; ts.poolBalances[id][to] += amount; emit TransferSingle(operator, address(0), to, id, amount); @@ -337,10 +364,13 @@ contract ERC1155WithTerminusStorage is uint256[] memory amounts, bytes memory data ) internal virtual { - require(to != address(0), "ERC1155: mint to the zero address"); + require( + to != address(0), + "ERC1155WithTerminusStorage: mint to the zero address" + ); require( ids.length == amounts.length, - "ERC1155: ids and amounts length mismatch" + "ERC1155WithTerminusStorage: ids and amounts length mismatch" ); LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage(); @@ -357,6 +387,7 @@ contract ERC1155WithTerminusStorage is _beforeTokenTransfer(operator, address(0), to, ids, amounts, data); for (uint256 i = 0; i < ids.length; i++) { + ts.poolSupply[ids[i]] += amounts[i]; ts.poolBalances[ids[i]][to] += amounts[i]; } @@ -385,10 +416,23 @@ contract ERC1155WithTerminusStorage is uint256 id, uint256 amount ) internal virtual { - require(from != address(0), "ERC1155: burn from the zero address"); + require( + from != address(0), + "ERC1155WithTerminusStorage: burn from the zero address" + ); + LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage(); + require( + ts.poolBurnable[id], + "ERC1155WithTerminusStorage: _burn -- pool is not burnable" + ); address operator = _msgSender(); + require( + operator == from || isApprovedForPool(id, operator), + "ERC1155WithTerminusStorage: _burn -- caller is neither owner nor approved" + ); + _beforeTokenTransfer( operator, from, @@ -398,11 +442,14 @@ contract ERC1155WithTerminusStorage is "" ); - LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage(); uint256 fromBalance = ts.poolBalances[id][from]; - require(fromBalance >= amount, "ERC1155: burn amount exceeds balance"); + require( + fromBalance >= amount, + "ERC1155WithTerminusStorage: burn amount exceeds balance" + ); unchecked { ts.poolBalances[id][from] = fromBalance - amount; + ts.poolSupply[id] -= amount; } emit TransferSingle(operator, from, address(0), id, amount); @@ -420,16 +467,35 @@ contract ERC1155WithTerminusStorage is uint256[] memory ids, uint256[] memory amounts ) internal virtual { - require(from != address(0), "ERC1155: burn from the zero address"); + require( + from != address(0), + "ERC1155WithTerminusStorage: burn from the zero address" + ); require( ids.length == amounts.length, - "ERC1155: ids and amounts length mismatch" + "ERC1155WithTerminusStorage: ids and amounts length mismatch" ); address operator = _msgSender(); - _beforeTokenTransfer(operator, from, address(0), ids, amounts, ""); + bool approvedForPools = true; + LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage(); + for (uint256 i = 0; i < ids.length; i++) { + require( + ts.poolBurnable[ids[i]], + "ERC1155WithTerminusStorage: _burnBatch -- pool is not burnable" + ); + if (!isApprovedForPool(ids[i], operator)) { + approvedForPools = false; + } + } + require( + from == _msgSender() || approvedForPools, + "ERC1155WithTerminusStorage: _burnBatch -- caller is neither owner nor approved" + ); + + _beforeTokenTransfer(operator, from, address(0), ids, amounts, ""); for (uint256 i = 0; i < ids.length; i++) { uint256 id = ids[i]; @@ -438,10 +504,11 @@ contract ERC1155WithTerminusStorage is uint256 fromBalance = ts.poolBalances[id][from]; require( fromBalance >= amount, - "ERC1155: burn amount exceeds balance" + "ERC1155WithTerminusStorage: burn amount exceeds balance" ); unchecked { ts.poolBalances[id][from] = fromBalance - amount; + ts.poolSupply[id] -= amount; } } @@ -458,7 +525,10 @@ contract ERC1155WithTerminusStorage is address operator, bool approved ) internal virtual { - require(owner != operator, "ERC1155: setting approval status for self"); + require( + owner != operator, + "ERC1155WithTerminusStorage: setting approval status for self" + ); LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage(); ts.globalOperatorApprovals[owner][operator] = approved; emit ApprovalForAll(owner, operator, approved); @@ -512,12 +582,16 @@ contract ERC1155WithTerminusStorage is ) returns (bytes4 response) { if (response != IERC1155Receiver.onERC1155Received.selector) { - revert("ERC1155: ERC1155Receiver rejected tokens"); + revert( + "ERC1155WithTerminusStorage: ERC1155Receiver rejected tokens" + ); } } catch Error(string memory reason) { revert(reason); } catch { - revert("ERC1155: transfer to non ERC1155Receiver implementer"); + revert( + "ERC1155WithTerminusStorage: transfer to non ERC1155Receiver implementer" + ); } } } @@ -543,12 +617,16 @@ contract ERC1155WithTerminusStorage is if ( response != IERC1155Receiver.onERC1155BatchReceived.selector ) { - revert("ERC1155: ERC1155Receiver rejected tokens"); + revert( + "ERC1155WithTerminusStorage: ERC1155Receiver rejected tokens" + ); } } catch Error(string memory reason) { revert(reason); } catch { - revert("ERC1155: transfer to non ERC1155Receiver implementer"); + revert( + "ERC1155WithTerminusStorage: transfer to non ERC1155Receiver implementer" + ); } } } diff --git a/contracts/terminus/LibTerminus.sol b/contracts/terminus/LibTerminus.sol index 529b86d..1b611ac 100644 --- a/contracts/terminus/LibTerminus.sol +++ b/contracts/terminus/LibTerminus.sol @@ -31,8 +31,9 @@ library LibTerminus { mapping(uint256 => uint256) poolCapacity; mapping(uint256 => uint256) poolSupply; mapping(uint256 => mapping(address => uint256)) poolBalances; + mapping(uint256 => bool) poolNotTransferable; + mapping(uint256 => bool) poolBurnable; mapping(address => mapping(address => bool)) globalOperatorApprovals; - // TODO(zomglings): Add mappings for pool properties. E.g. poolTransferable, poolMintable, etc. } function terminusStorage() diff --git a/contracts/terminus/TerminusFacet.sol b/contracts/terminus/TerminusFacet.sol index 7a86d2d..4bdb417 100644 --- a/contracts/terminus/TerminusFacet.sol +++ b/contracts/terminus/TerminusFacet.sol @@ -12,7 +12,9 @@ * 3. Pool capacity: The total number of tokens that can be minted in that authorization pool. * 4. Pool supply: The number of tokens that have actually been minted in that authorization pool. * 5. Transferable: A boolean value which denotes whether or not tokens from that pool can be transfered - * between addresses. + * between addresses. (Note: Implemented by TerminusStorage.poolNotTransferable since we expect most + * pools to be transferable. This negation is better for storage + gas since false is default value + * in map to bool.) * 6. Burnable: A boolean value which denotes whether or not tokens from that pool can be burned. */ @@ -97,6 +99,14 @@ contract TerminusFacet is ERC1155WithTerminusStorage { return LibTerminus.terminusStorage().poolCapacity[poolID]; } + function terminusPoolSupply(uint256 poolID) + external + view + returns (uint256) + { + return LibTerminus.terminusStorage().poolSupply[poolID]; + } + function createSimplePool(uint256 _capacity) external returns (uint256) { LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage(); uint256 requiredPayment = ts.poolBasePrice; @@ -114,6 +124,35 @@ contract TerminusFacet is ERC1155WithTerminusStorage { return LibTerminus.createSimplePool(_capacity); } + function createPoolV1( + uint256 _capacity, + bool _transferable, + bool _burnable + ) external returns (uint256) { + LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage(); + // TODO(zomglings): Implement requiredPayment update based on pool features. + uint256 requiredPayment = ts.poolBasePrice; + IERC20 paymentTokenContract = _paymentTokenContract(); + require( + paymentTokenContract.allowance(_msgSender(), address(this)) >= + requiredPayment, + "TerminusFacet: createPoolV1 -- Insufficient allowance on payment token" + ); + paymentTokenContract.transferFrom( + msg.sender, + address(this), + requiredPayment + ); + uint256 poolID = LibTerminus.createSimplePool(_capacity); + if (!_transferable) { + ts.poolNotTransferable[poolID] = true; + } + if (_burnable) { + ts.poolBurnable[poolID] = true; + } + return poolID; + } + function mint( address to, uint256 poolID, @@ -135,4 +174,12 @@ contract TerminusFacet is ERC1155WithTerminusStorage { } _mintBatch(to, poolIDs, amounts, data); } + + function burn( + address from, + uint256 poolID, + uint256 amount + ) external { + _burn(from, poolID, amount); + } } diff --git a/dao/TerminusFacet.py b/dao/TerminusFacet.py index 41f3fad..7cedded 100644 --- a/dao/TerminusFacet.py +++ b/dao/TerminusFacet.py @@ -98,6 +98,20 @@ class TerminusFacet: self.assert_contract_is_instantiated() return self.contract.balanceOfBatch.call(accounts, ids) + def burn( + self, from_: ChecksumAddress, pool_id: int, amount: int, transaction_config + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.burn(from_, pool_id, amount, transaction_config) + + def create_pool_v1( + self, _capacity: int, _transferable: bool, _burnable: bool, transaction_config + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.createPoolV1( + _capacity, _transferable, _burnable, transaction_config + ) + def create_simple_pool(self, _capacity: int, transaction_config) -> Any: self.assert_contract_is_instantiated() return self.contract.createSimplePool(_capacity, transaction_config) @@ -108,6 +122,10 @@ class TerminusFacet: self.assert_contract_is_instantiated() return self.contract.isApprovedForAll.call(account, operator) + def is_approved_for_pool(self, pool_id: int, operator: ChecksumAddress) -> Any: + self.assert_contract_is_instantiated() + return self.contract.isApprovedForPool.call(pool_id, operator) + def mint( self, to: ChecksumAddress, @@ -202,6 +220,10 @@ class TerminusFacet: self.assert_contract_is_instantiated() return self.contract.terminusPoolController.call(pool_id) + def terminus_pool_supply(self, pool_id: int) -> Any: + self.assert_contract_is_instantiated() + return self.contract.terminusPoolSupply.call(pool_id) + def total_pools(self) -> Any: self.assert_contract_is_instantiated() return self.contract.totalPools.call() @@ -282,6 +304,32 @@ def handle_balance_of_batch(args: argparse.Namespace) -> None: print(result) +def handle_burn(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = TerminusFacet(args.address) + transaction_config = get_transaction_config(args) + result = contract.burn( + from_=args.from_arg, + pool_id=args.pool_id, + amount=args.amount, + transaction_config=transaction_config, + ) + print(result) + + +def handle_create_pool_v1(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = TerminusFacet(args.address) + transaction_config = get_transaction_config(args) + result = contract.create_pool_v1( + _capacity=args.capacity_arg, + _transferable=args.transferable_arg, + _burnable=args.burnable_arg, + transaction_config=transaction_config, + ) + print(result) + + def handle_create_simple_pool(args: argparse.Namespace) -> None: network.connect(args.network) contract = TerminusFacet(args.address) @@ -299,6 +347,13 @@ def handle_is_approved_for_all(args: argparse.Namespace) -> None: print(result) +def handle_is_approved_for_pool(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = TerminusFacet(args.address) + result = contract.is_approved_for_pool(pool_id=args.pool_id, operator=args.operator) + print(result) + + def handle_mint(args: argparse.Namespace) -> None: network.connect(args.network) contract = TerminusFacet(args.address) @@ -443,6 +498,13 @@ def handle_terminus_pool_controller(args: argparse.Namespace) -> None: print(result) +def handle_terminus_pool_supply(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = TerminusFacet(args.address) + result = contract.terminus_pool_supply(pool_id=args.pool_id) + print(result) + + def handle_total_pools(args: argparse.Namespace) -> None: network.connect(args.network) contract = TerminusFacet(args.address) @@ -496,6 +558,29 @@ def generate_cli() -> argparse.ArgumentParser: ) balance_of_batch_parser.set_defaults(func=handle_balance_of_batch) + burn_parser = subcommands.add_parser("burn") + add_default_arguments(burn_parser, True) + burn_parser.add_argument("--from-arg", required=True, help="Type: address") + burn_parser.add_argument("--pool-id", required=True, help="Type: uint256", type=int) + burn_parser.add_argument("--amount", required=True, help="Type: uint256", type=int) + burn_parser.set_defaults(func=handle_burn) + + create_pool_v1_parser = subcommands.add_parser("create-pool-v1") + add_default_arguments(create_pool_v1_parser, True) + create_pool_v1_parser.add_argument( + "--capacity-arg", required=True, help="Type: uint256", type=int + ) + create_pool_v1_parser.add_argument( + "--transferable-arg", + required=True, + help="Type: bool", + type=boolean_argument_type, + ) + create_pool_v1_parser.add_argument( + "--burnable-arg", required=True, help="Type: bool", type=boolean_argument_type + ) + create_pool_v1_parser.set_defaults(func=handle_create_pool_v1) + create_simple_pool_parser = subcommands.add_parser("create-simple-pool") add_default_arguments(create_simple_pool_parser, True) create_simple_pool_parser.add_argument( @@ -513,6 +598,16 @@ def generate_cli() -> argparse.ArgumentParser: ) is_approved_for_all_parser.set_defaults(func=handle_is_approved_for_all) + is_approved_for_pool_parser = subcommands.add_parser("is-approved-for-pool") + add_default_arguments(is_approved_for_pool_parser, False) + is_approved_for_pool_parser.add_argument( + "--pool-id", required=True, help="Type: uint256", type=int + ) + is_approved_for_pool_parser.add_argument( + "--operator", required=True, help="Type: address" + ) + is_approved_for_pool_parser.set_defaults(func=handle_is_approved_for_pool) + mint_parser = subcommands.add_parser("mint") add_default_arguments(mint_parser, True) mint_parser.add_argument("--to", required=True, help="Type: address") @@ -640,6 +735,13 @@ def generate_cli() -> argparse.ArgumentParser: ) terminus_pool_controller_parser.set_defaults(func=handle_terminus_pool_controller) + terminus_pool_supply_parser = subcommands.add_parser("terminus-pool-supply") + add_default_arguments(terminus_pool_supply_parser, False) + terminus_pool_supply_parser.add_argument( + "--pool-id", required=True, help="Type: uint256", type=int + ) + terminus_pool_supply_parser.set_defaults(func=handle_terminus_pool_supply) + total_pools_parser = subcommands.add_parser("total-pools") add_default_arguments(total_pools_parser, False) total_pools_parser.set_defaults(func=handle_total_pools) diff --git a/dao/test_terminus.py b/dao/test_terminus.py index f26a79b..cfa66bb 100644 --- a/dao/test_terminus.py +++ b/dao/test_terminus.py @@ -118,5 +118,364 @@ class TestPoolCreation(TerminusTestCase): self.assertEqual(pool_capacity, 10) +class TestPoolOperations(TerminusTestCase): + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + moonstream_diamond_address = cls.contracts["Diamond"] + diamond_moonstream = ERC20Facet.ERC20Facet(moonstream_diamond_address) + terminus_diamond_address = cls.terminus_contracts["Diamond"] + diamond_terminus = TerminusFacet.TerminusFacet(terminus_diamond_address) + diamond_terminus.set_payment_token( + moonstream_diamond_address, {"from": accounts[0]} + ) + diamond_terminus.set_pool_base_price(1000, {"from": accounts[0]}) + diamond_moonstream.mint(accounts[1], 1000000, {"from": accounts[0]}) + diamond_moonstream.approve( + terminus_diamond_address, 1000000, {"from": accounts[1]} + ) + cls.diamond_terminus = diamond_terminus + cls.diamond_moonstream = diamond_moonstream + + def setUp(self) -> None: + self.diamond_terminus.create_simple_pool(10, {"from": accounts[1]}) + + def test_mint(self): + pool_id = self.diamond_terminus.total_pools() + self.diamond_terminus.mint(accounts[2], pool_id, 1, b"", {"from": accounts[1]}) + + balance = self.diamond_terminus.balance_of(accounts[2].address, pool_id) + self.assertEqual(balance, 1) + + supply = self.diamond_terminus.terminus_pool_supply(pool_id) + self.assertEqual(supply, 1) + + def test_mint_fails_if_it_exceeds_capacity(self): + pool_id = self.diamond_terminus.total_pools() + with self.assertRaises(Exception): + self.diamond_terminus.mint( + accounts[2], pool_id, 11, b"", {"from": accounts[1]} + ) + + balance = self.diamond_terminus.balance_of(accounts[2].address, pool_id) + self.assertEqual(balance, 0) + + supply = self.diamond_terminus.terminus_pool_supply(pool_id) + self.assertEqual(supply, 0) + + def test_mint_batch(self): + pool_id = self.diamond_terminus.total_pools() + self.diamond_terminus.mint_batch( + accounts[2].address, + pool_i_ds=[pool_id], + amounts=[1], + data=b"", + transaction_config={"from": accounts[1]}, + ) + + balance = self.diamond_terminus.balance_of(accounts[2].address, pool_id) + self.assertEqual(balance, 1) + + supply = self.diamond_terminus.terminus_pool_supply(pool_id) + self.assertEqual(supply, 1) + + def test_mint_batch_fails_if_it_exceeds_capacity(self): + pool_id = self.diamond_terminus.total_pools() + with self.assertRaises(Exception): + self.diamond_terminus.mint_batch( + accounts[2].address, + pool_i_ds=[pool_id], + amounts=[11], + data=b"", + transaction_config={"from": accounts[1]}, + ) + + balance = self.diamond_terminus.balance_of(accounts[2].address, pool_id) + self.assertEqual(balance, 0) + + supply = self.diamond_terminus.terminus_pool_supply(pool_id) + self.assertEqual(supply, 0) + + def test_transfer(self): + pool_id = self.diamond_terminus.total_pools() + self.diamond_terminus.mint(accounts[2], pool_id, 1, b"", {"from": accounts[1]}) + + initial_sender_balance = self.diamond_terminus.balance_of( + accounts[2].address, pool_id + ) + initial_receiver_balance = self.diamond_terminus.balance_of( + accounts[3].address, pool_id + ) + + self.diamond_terminus.safe_transfer_from( + accounts[2].address, + accounts[3].address, + pool_id, + 1, + b"", + {"from": accounts[2]}, + ) + + final_sender_balance = self.diamond_terminus.balance_of( + accounts[2].address, pool_id + ) + final_receiver_balance = self.diamond_terminus.balance_of( + accounts[3].address, pool_id + ) + + self.assertEqual(final_sender_balance, initial_sender_balance - 1) + self.assertEqual(final_receiver_balance, initial_receiver_balance + 1) + + def test_transfer_as_pool_controller(self): + pool_id = self.diamond_terminus.total_pools() + self.diamond_terminus.mint(accounts[2], pool_id, 1, b"", {"from": accounts[1]}) + + initial_sender_balance = self.diamond_terminus.balance_of( + accounts[2].address, pool_id + ) + initial_receiver_balance = self.diamond_terminus.balance_of( + accounts[3].address, pool_id + ) + + self.diamond_terminus.safe_transfer_from( + accounts[2].address, + accounts[3].address, + pool_id, + 1, + b"", + {"from": accounts[1]}, + ) + + final_sender_balance = self.diamond_terminus.balance_of( + accounts[2].address, pool_id + ) + final_receiver_balance = self.diamond_terminus.balance_of( + accounts[3].address, pool_id + ) + + self.assertEqual(final_sender_balance, initial_sender_balance - 1) + self.assertEqual(final_receiver_balance, initial_receiver_balance + 1) + + def test_transfer_as_unauthorized_recipient(self): + pool_id = self.diamond_terminus.total_pools() + self.diamond_terminus.mint(accounts[2], pool_id, 1, b"", {"from": accounts[1]}) + + initial_sender_balance = self.diamond_terminus.balance_of( + accounts[2].address, pool_id + ) + initial_receiver_balance = self.diamond_terminus.balance_of( + accounts[3].address, pool_id + ) + + with self.assertRaises(Exception): + self.diamond_terminus.safe_transfer_from( + accounts[2].address, + accounts[3].address, + pool_id, + 1, + b"", + {"from": accounts[3]}, + ) + + final_sender_balance = self.diamond_terminus.balance_of( + accounts[2].address, pool_id + ) + final_receiver_balance = self.diamond_terminus.balance_of( + accounts[3].address, pool_id + ) + + self.assertEqual(final_sender_balance, initial_sender_balance) + self.assertEqual(final_receiver_balance, initial_receiver_balance) + + def test_transfer_as_unauthorized_unrelated_party(self): + pool_id = self.diamond_terminus.total_pools() + self.diamond_terminus.mint(accounts[2], pool_id, 1, b"", {"from": accounts[1]}) + + initial_sender_balance = self.diamond_terminus.balance_of( + accounts[2].address, pool_id + ) + initial_receiver_balance = self.diamond_terminus.balance_of( + accounts[3].address, pool_id + ) + + with self.assertRaises(Exception): + self.diamond_terminus.safe_transfer_from( + accounts[2].address, + accounts[3].address, + pool_id, + 1, + b"", + {"from": accounts[4]}, + ) + + final_sender_balance = self.diamond_terminus.balance_of( + accounts[2].address, pool_id + ) + final_receiver_balance = self.diamond_terminus.balance_of( + accounts[3].address, pool_id + ) + + self.assertEqual(final_sender_balance, initial_sender_balance) + self.assertEqual(final_receiver_balance, initial_receiver_balance) + + def test_burn_fails_as_token_owner(self): + pool_id = self.diamond_terminus.total_pools() + self.diamond_terminus.mint(accounts[2], pool_id, 1, b"", {"from": accounts[1]}) + + initial_pool_supply = self.diamond_terminus.terminus_pool_supply(pool_id) + initial_owner_balance = self.diamond_terminus.balance_of( + accounts[2].address, pool_id + ) + with self.assertRaises(Exception): + self.diamond_terminus.burn( + accounts[2].address, pool_id, 1, {"from": accounts[2]} + ) + + final_pool_supply = self.diamond_terminus.terminus_pool_supply(pool_id) + final_owner_balance = self.diamond_terminus.balance_of( + accounts[2].address, pool_id + ) + self.assertEqual(final_pool_supply, initial_pool_supply) + self.assertEqual(final_owner_balance, initial_owner_balance) + + def test_burn_fails_as_pool_controller(self): + pool_id = self.diamond_terminus.total_pools() + self.diamond_terminus.mint(accounts[2], pool_id, 1, b"", {"from": accounts[1]}) + + initial_pool_supply = self.diamond_terminus.terminus_pool_supply(pool_id) + initial_owner_balance = self.diamond_terminus.balance_of( + accounts[2].address, pool_id + ) + with self.assertRaises(Exception): + self.diamond_terminus.burn( + accounts[2].address, pool_id, 1, {"from": accounts[1]} + ) + + final_pool_supply = self.diamond_terminus.terminus_pool_supply(pool_id) + final_owner_balance = self.diamond_terminus.balance_of( + accounts[2].address, pool_id + ) + self.assertEqual(final_pool_supply, initial_pool_supply) + self.assertEqual(final_owner_balance, initial_owner_balance) + + def test_burn_fails_as_third_party(self): + pool_id = self.diamond_terminus.total_pools() + self.diamond_terminus.mint(accounts[2], pool_id, 1, b"", {"from": accounts[1]}) + + initial_pool_supply = self.diamond_terminus.terminus_pool_supply(pool_id) + initial_owner_balance = self.diamond_terminus.balance_of( + accounts[2].address, pool_id + ) + with self.assertRaises(Exception): + self.diamond_terminus.burn( + accounts[2].address, pool_id, 1, {"from": accounts[3]} + ) + + final_pool_supply = self.diamond_terminus.terminus_pool_supply(pool_id) + final_owner_balance = self.diamond_terminus.balance_of( + accounts[2].address, pool_id + ) + self.assertEqual(final_pool_supply, initial_pool_supply) + self.assertEqual(final_owner_balance, initial_owner_balance) + + +class TestCreatePoolV1(TestPoolOperations): + def setUp(self): + self.diamond_terminus.create_pool_v1(10, True, False, {"from": accounts[1]}) + + def test_nontransferable_pool(self): + self.diamond_terminus.create_pool_v1(10, False, False, {"from": accounts[1]}) + pool_id = self.diamond_terminus.total_pools() + self.diamond_terminus.mint(accounts[2], pool_id, 1, b"", {"from": accounts[1]}) + + initial_sender_balance = self.diamond_terminus.balance_of( + accounts[2].address, pool_id + ) + initial_receiver_balance = self.diamond_terminus.balance_of( + accounts[3].address, pool_id + ) + + with self.assertRaises(Exception): + self.diamond_terminus.safe_transfer_from( + accounts[2].address, + accounts[3].address, + pool_id, + 1, + b"", + {"from": accounts[2]}, + ) + + final_sender_balance = self.diamond_terminus.balance_of( + accounts[2].address, pool_id + ) + final_receiver_balance = self.diamond_terminus.balance_of( + accounts[3].address, pool_id + ) + + self.assertEqual(final_sender_balance, initial_sender_balance) + self.assertEqual(final_receiver_balance, initial_receiver_balance) + + def test_burnable_pool_burn_as_token_owner(self): + self.diamond_terminus.create_pool_v1(10, True, True, {"from": accounts[1]}) + pool_id = self.diamond_terminus.total_pools() + self.diamond_terminus.mint(accounts[2], pool_id, 1, b"", {"from": accounts[1]}) + + initial_pool_supply = self.diamond_terminus.terminus_pool_supply(pool_id) + initial_owner_balance = self.diamond_terminus.balance_of( + accounts[2].address, pool_id + ) + self.diamond_terminus.burn( + accounts[2].address, pool_id, 1, {"from": accounts[2]} + ) + + final_pool_supply = self.diamond_terminus.terminus_pool_supply(pool_id) + final_owner_balance = self.diamond_terminus.balance_of( + accounts[2].address, pool_id + ) + self.assertEqual(final_pool_supply, initial_pool_supply - 1) + self.assertEqual(final_owner_balance, initial_owner_balance - 1) + + def test_burnable_pool_burn_as_pool_controller(self): + self.diamond_terminus.create_pool_v1(10, True, True, {"from": accounts[1]}) + pool_id = self.diamond_terminus.total_pools() + self.diamond_terminus.mint(accounts[2], pool_id, 1, b"", {"from": accounts[1]}) + + initial_pool_supply = self.diamond_terminus.terminus_pool_supply(pool_id) + initial_owner_balance = self.diamond_terminus.balance_of( + accounts[2].address, pool_id + ) + self.diamond_terminus.burn( + accounts[2].address, pool_id, 1, {"from": accounts[1]} + ) + + final_pool_supply = self.diamond_terminus.terminus_pool_supply(pool_id) + final_owner_balance = self.diamond_terminus.balance_of( + accounts[2].address, pool_id + ) + self.assertEqual(final_pool_supply, initial_pool_supply - 1) + self.assertEqual(final_owner_balance, initial_owner_balance - 1) + + def test_burnable_pool_burn_as_unauthorized_third_party(self): + self.diamond_terminus.create_pool_v1(10, True, True, {"from": accounts[1]}) + pool_id = self.diamond_terminus.total_pools() + self.diamond_terminus.mint(accounts[2], pool_id, 1, b"", {"from": accounts[1]}) + + initial_pool_supply = self.diamond_terminus.terminus_pool_supply(pool_id) + initial_owner_balance = self.diamond_terminus.balance_of( + accounts[2].address, pool_id + ) + with self.assertRaises(Exception): + self.diamond_terminus.burn( + accounts[2].address, pool_id, 1, {"from": accounts[3]} + ) + + final_pool_supply = self.diamond_terminus.terminus_pool_supply(pool_id) + final_owner_balance = self.diamond_terminus.balance_of( + accounts[2].address, pool_id + ) + self.assertEqual(final_pool_supply, initial_pool_supply) + self.assertEqual(final_owner_balance, initial_owner_balance) + + if __name__ == "__main__": unittest.main() From 6ec5715996005b89ee6344b60181a60d264458b3 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Wed, 22 Dec 2021 12:10:32 -0800 Subject: [PATCH 17/18] Added pool authorizations for separate addresses --- .../terminus/ERC1155WithTerminusStorage.sol | 25 +-- contracts/terminus/LibTerminus.sol | 20 ++ contracts/terminus/TerminusFacet.sol | 51 ++++++ dao/TerminusFacet.py | 62 +++++++ dao/test_terminus.py | 173 ++++++++++++++++++ 5 files changed, 312 insertions(+), 19 deletions(-) diff --git a/contracts/terminus/ERC1155WithTerminusStorage.sol b/contracts/terminus/ERC1155WithTerminusStorage.sol index 7728299..151722e 100644 --- a/contracts/terminus/ERC1155WithTerminusStorage.sol +++ b/contracts/terminus/ERC1155WithTerminusStorage.sol @@ -140,11 +140,12 @@ contract ERC1155WithTerminusStorage is view returns (bool) { - LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage(); - if (operator == ts.poolController[poolID]) { - return true; - } - return false; + return LibTerminus._isApprovedForPool(poolID, operator); + } + + function approveForPool(uint256 poolID, address operator) external { + LibTerminus.enforcePoolIsController(poolID, _msgSender()); + LibTerminus._approveForPool(poolID, operator); } /** @@ -428,11 +429,6 @@ contract ERC1155WithTerminusStorage is address operator = _msgSender(); - require( - operator == from || isApprovedForPool(id, operator), - "ERC1155WithTerminusStorage: _burn -- caller is neither owner nor approved" - ); - _beforeTokenTransfer( operator, from, @@ -478,22 +474,13 @@ contract ERC1155WithTerminusStorage is address operator = _msgSender(); - bool approvedForPools = true; - LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage(); for (uint256 i = 0; i < ids.length; i++) { require( ts.poolBurnable[ids[i]], "ERC1155WithTerminusStorage: _burnBatch -- pool is not burnable" ); - if (!isApprovedForPool(ids[i], operator)) { - approvedForPools = false; - } } - require( - from == _msgSender() || approvedForPools, - "ERC1155WithTerminusStorage: _burnBatch -- caller is neither owner nor approved" - ); _beforeTokenTransfer(operator, from, address(0), ids, amounts, ""); diff --git a/contracts/terminus/LibTerminus.sol b/contracts/terminus/LibTerminus.sol index 1b611ac..e6ff041 100644 --- a/contracts/terminus/LibTerminus.sol +++ b/contracts/terminus/LibTerminus.sol @@ -34,6 +34,7 @@ library LibTerminus { mapping(uint256 => bool) poolNotTransferable; mapping(uint256 => bool) poolBurnable; mapping(address => mapping(address => bool)) globalOperatorApprovals; + mapping(uint256 => mapping(address => bool)) globalPoolOperatorApprovals; } function terminusStorage() @@ -101,4 +102,23 @@ library LibTerminus { "LibTerminus: Must be pool controller" ); } + + function _isApprovedForPool(uint256 poolID, address operator) + internal + view + returns (bool) + { + LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage(); + if (operator == ts.poolController[poolID]) { + return true; + } else if (ts.globalPoolOperatorApprovals[poolID][operator]) { + return true; + } + return false; + } + + function _approveForPool(uint256 poolID, address operator) internal { + LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage(); + ts.globalPoolOperatorApprovals[poolID][operator] = true; + } } diff --git a/contracts/terminus/TerminusFacet.sol b/contracts/terminus/TerminusFacet.sol index 4bdb417..f6087a2 100644 --- a/contracts/terminus/TerminusFacet.sol +++ b/contracts/terminus/TerminusFacet.sol @@ -28,6 +28,52 @@ import "../diamond/libraries/LibDiamond.sol"; contract TerminusFacet is ERC1155WithTerminusStorage { constructor() {} + event PoolMintBatch( + uint256 indexed id, + address indexed operator, + address from, + address[] toAddresses, + uint256[] amounts + ); + + function poolMintBatch( + uint256 id, + address[] memory toAddresses, + uint256[] memory amounts + ) public { + address operator = _msgSender(); + LibTerminus.enforcePoolIsController(id, operator); + require( + toAddresses.length == amounts.length, + "TerminusFacet: _poolMintBatch -- toAddresses and amounts length mismatch" + ); + + LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage(); + + uint256 i = 0; + uint256 totalAmount = 0; + + for (i = 0; i < toAddresses.length; i++) { + address to = toAddresses[i]; + uint256 amount = amounts[i]; + require( + to != address(0), + "TerminusFacet: _poolMintBatch -- cannot mint to zero address" + ); + totalAmount += amount; + ts.poolBalances[id][to] += amount; + emit TransferSingle(operator, address(0), to, id, amount); + } + + require( + ts.poolSupply[id] + totalAmount <= ts.poolCapacity[id], + "TerminusFacet: _poolMintBatch -- Minted tokens would exceed pool capacity" + ); + ts.poolSupply[id] += totalAmount; + + emit PoolMintBatch(id, operator, address(0), toAddresses, amounts); + } + function terminusController() external view returns (address) { return LibTerminus.terminusStorage().controller; } @@ -180,6 +226,11 @@ contract TerminusFacet is ERC1155WithTerminusStorage { uint256 poolID, uint256 amount ) external { + address operator = _msgSender(); + require( + operator == from || isApprovedForPool(poolID, operator), + "TerminusFacet: burn -- caller is neither owner nor approved" + ); _burn(from, poolID, amount); } } diff --git a/dao/TerminusFacet.py b/dao/TerminusFacet.py index 7cedded..f02707a 100644 --- a/dao/TerminusFacet.py +++ b/dao/TerminusFacet.py @@ -90,6 +90,12 @@ class TerminusFacet: if self.contract is None: raise Exception("contract has not been instantiated") + def approve_for_pool( + self, pool_id: int, operator: ChecksumAddress, transaction_config + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.approveForPool(pool_id, operator, transaction_config) + def balance_of(self, account: ChecksumAddress, id: int) -> Any: self.assert_contract_is_instantiated() return self.contract.balanceOf.call(account, id) @@ -156,6 +162,14 @@ class TerminusFacet: self.assert_contract_is_instantiated() return self.contract.poolBasePrice.call() + def pool_mint_batch( + self, id: int, to_addresses: List, amounts: List, transaction_config + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.poolMintBatch( + id, to_addresses, amounts, transaction_config + ) + def safe_batch_transfer_from( self, from_: ChecksumAddress, @@ -290,6 +304,18 @@ def handle_deploy(args: argparse.Namespace) -> None: print(result) +def handle_approve_for_pool(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = TerminusFacet(args.address) + transaction_config = get_transaction_config(args) + result = contract.approve_for_pool( + pool_id=args.pool_id, + operator=args.operator, + transaction_config=transaction_config, + ) + print(result) + + def handle_balance_of(args: argparse.Namespace) -> None: network.connect(args.network) contract = TerminusFacet(args.address) @@ -396,6 +422,19 @@ def handle_pool_base_price(args: argparse.Namespace) -> None: print(result) +def handle_pool_mint_batch(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = TerminusFacet(args.address) + transaction_config = get_transaction_config(args) + result = contract.pool_mint_batch( + id=args.id, + to_addresses=args.to_addresses, + amounts=args.amounts, + transaction_config=transaction_config, + ) + print(result) + + def handle_safe_batch_transfer_from(args: argparse.Namespace) -> None: network.connect(args.network) contract = TerminusFacet(args.address) @@ -540,6 +579,16 @@ def generate_cli() -> argparse.ArgumentParser: add_default_arguments(deploy_parser, True) deploy_parser.set_defaults(func=handle_deploy) + approve_for_pool_parser = subcommands.add_parser("approve-for-pool") + add_default_arguments(approve_for_pool_parser, True) + approve_for_pool_parser.add_argument( + "--pool-id", required=True, help="Type: uint256", type=int + ) + approve_for_pool_parser.add_argument( + "--operator", required=True, help="Type: address" + ) + approve_for_pool_parser.set_defaults(func=handle_approve_for_pool) + balance_of_parser = subcommands.add_parser("balance-of") add_default_arguments(balance_of_parser, False) balance_of_parser.add_argument("--account", required=True, help="Type: address") @@ -640,6 +689,19 @@ def generate_cli() -> argparse.ArgumentParser: add_default_arguments(pool_base_price_parser, False) pool_base_price_parser.set_defaults(func=handle_pool_base_price) + pool_mint_batch_parser = subcommands.add_parser("pool-mint-batch") + add_default_arguments(pool_mint_batch_parser, True) + pool_mint_batch_parser.add_argument( + "--id", required=True, help="Type: uint256", type=int + ) + pool_mint_batch_parser.add_argument( + "--to-addresses", required=True, help="Type: address[]", nargs="+" + ) + pool_mint_batch_parser.add_argument( + "--amounts", required=True, help="Type: uint256[]", nargs="+" + ) + pool_mint_batch_parser.set_defaults(func=handle_pool_mint_batch) + safe_batch_transfer_from_parser = subcommands.add_parser("safe-batch-transfer-from") add_default_arguments(safe_batch_transfer_from_parser, True) safe_batch_transfer_from_parser.add_argument( diff --git a/dao/test_terminus.py b/dao/test_terminus.py index cfa66bb..f49df06 100644 --- a/dao/test_terminus.py +++ b/dao/test_terminus.py @@ -1,3 +1,4 @@ +from typing import List import unittest from brownie import accounts @@ -196,6 +197,66 @@ class TestPoolOperations(TerminusTestCase): supply = self.diamond_terminus.terminus_pool_supply(pool_id) self.assertEqual(supply, 0) + def test_pool_mint_batch(self): + pool_id = self.diamond_terminus.total_pools() + target_accounts = [account.address for account in accounts[:5]] + target_amounts = [1 for _ in accounts[:5]] + num_accounts = len(accounts[:5]) + initial_pool_supply = self.diamond_terminus.terminus_pool_supply(pool_id) + initial_balances: List[int] = [] + for account in accounts[:5]: + initial_balances.append( + self.diamond_terminus.balance_of(account.address, pool_id) + ) + self.diamond_terminus.pool_mint_batch( + pool_id, target_accounts, target_amounts, {"from": accounts[1]} + ) + final_pool_supply = self.diamond_terminus.terminus_pool_supply(pool_id) + self.assertEqual(final_pool_supply, initial_pool_supply + num_accounts) + for i, account in enumerate(accounts[:5]): + final_balance = self.diamond_terminus.balance_of(account.address, pool_id) + self.assertEqual(final_balance, initial_balances[i] + 1) + + def test_pool_mint_batch_as_contract_controller_not_pool_controller(self): + pool_id = self.diamond_terminus.total_pools() + target_accounts = [account.address for account in accounts[:5]] + target_amounts = [1 for _ in accounts[:5]] + initial_pool_supply = self.diamond_terminus.terminus_pool_supply(pool_id) + initial_balances: List[int] = [] + for account in accounts[:5]: + initial_balances.append( + self.diamond_terminus.balance_of(account.address, pool_id) + ) + with self.assertRaises(Exception): + self.diamond_terminus.pool_mint_batch( + pool_id, target_accounts, target_amounts, {"from": accounts[0]} + ) + final_pool_supply = self.diamond_terminus.terminus_pool_supply(pool_id) + self.assertEqual(final_pool_supply, initial_pool_supply) + for i, account in enumerate(accounts[:5]): + final_balance = self.diamond_terminus.balance_of(account.address, pool_id) + self.assertEqual(final_balance, initial_balances[i]) + + def test_pool_mint_batch_as_unauthorized_third_party(self): + pool_id = self.diamond_terminus.total_pools() + target_accounts = [account.address for account in accounts[:5]] + target_amounts = [1 for _ in accounts[:5]] + initial_pool_supply = self.diamond_terminus.terminus_pool_supply(pool_id) + initial_balances: List[int] = [] + for account in accounts[:5]: + initial_balances.append( + self.diamond_terminus.balance_of(account.address, pool_id) + ) + with self.assertRaises(Exception): + self.diamond_terminus.pool_mint_batch( + pool_id, target_accounts, target_amounts, {"from": accounts[2]} + ) + final_pool_supply = self.diamond_terminus.terminus_pool_supply(pool_id) + self.assertEqual(final_pool_supply, initial_pool_supply) + for i, account in enumerate(accounts[:5]): + final_balance = self.diamond_terminus.balance_of(account.address, pool_id) + self.assertEqual(final_balance, initial_balances[i]) + def test_transfer(self): pool_id = self.diamond_terminus.total_pools() self.diamond_terminus.mint(accounts[2], pool_id, 1, b"", {"from": accounts[1]}) @@ -287,6 +348,39 @@ class TestPoolOperations(TerminusTestCase): self.assertEqual(final_sender_balance, initial_sender_balance) self.assertEqual(final_receiver_balance, initial_receiver_balance) + def test_transfer_as_authorized_recipient(self): + pool_id = self.diamond_terminus.total_pools() + self.diamond_terminus.mint(accounts[2], pool_id, 1, b"", {"from": accounts[1]}) + + initial_sender_balance = self.diamond_terminus.balance_of( + accounts[2].address, pool_id + ) + initial_receiver_balance = self.diamond_terminus.balance_of( + accounts[3].address, pool_id + ) + + self.diamond_terminus.approve_for_pool( + pool_id, accounts[3].address, {"from": accounts[1]} + ) + self.diamond_terminus.safe_transfer_from( + accounts[2].address, + accounts[3].address, + pool_id, + 1, + b"", + {"from": accounts[3]}, + ) + + final_sender_balance = self.diamond_terminus.balance_of( + accounts[2].address, pool_id + ) + final_receiver_balance = self.diamond_terminus.balance_of( + accounts[3].address, pool_id + ) + + self.assertEqual(final_sender_balance, initial_sender_balance - 1) + self.assertEqual(final_receiver_balance, initial_receiver_balance + 1) + def test_transfer_as_unauthorized_unrelated_party(self): pool_id = self.diamond_terminus.total_pools() self.diamond_terminus.mint(accounts[2], pool_id, 1, b"", {"from": accounts[1]}) @@ -318,6 +412,39 @@ class TestPoolOperations(TerminusTestCase): self.assertEqual(final_sender_balance, initial_sender_balance) self.assertEqual(final_receiver_balance, initial_receiver_balance) + def test_transfer_as_authorized_unrelated_party(self): + pool_id = self.diamond_terminus.total_pools() + self.diamond_terminus.mint(accounts[2], pool_id, 1, b"", {"from": accounts[1]}) + + initial_sender_balance = self.diamond_terminus.balance_of( + accounts[2].address, pool_id + ) + initial_receiver_balance = self.diamond_terminus.balance_of( + accounts[3].address, pool_id + ) + + self.diamond_terminus.approve_for_pool( + pool_id, accounts[4].address, {"from": accounts[1]} + ) + self.diamond_terminus.safe_transfer_from( + accounts[2].address, + accounts[3].address, + pool_id, + 1, + b"", + {"from": accounts[4]}, + ) + + final_sender_balance = self.diamond_terminus.balance_of( + accounts[2].address, pool_id + ) + final_receiver_balance = self.diamond_terminus.balance_of( + accounts[3].address, pool_id + ) + + self.assertEqual(final_sender_balance, initial_sender_balance - 1) + self.assertEqual(final_receiver_balance, initial_receiver_balance + 1) + def test_burn_fails_as_token_owner(self): pool_id = self.diamond_terminus.total_pools() self.diamond_terminus.mint(accounts[2], pool_id, 1, b"", {"from": accounts[1]}) @@ -378,6 +505,29 @@ class TestPoolOperations(TerminusTestCase): self.assertEqual(final_pool_supply, initial_pool_supply) self.assertEqual(final_owner_balance, initial_owner_balance) + def test_burn_fails_as_authorized_third_party(self): + pool_id = self.diamond_terminus.total_pools() + self.diamond_terminus.mint(accounts[2], pool_id, 1, b"", {"from": accounts[1]}) + + initial_pool_supply = self.diamond_terminus.terminus_pool_supply(pool_id) + initial_owner_balance = self.diamond_terminus.balance_of( + accounts[2].address, pool_id + ) + self.diamond_terminus.approve_for_pool( + pool_id, accounts[3].address, {"from": accounts[1]} + ) + with self.assertRaises(Exception): + self.diamond_terminus.burn( + accounts[2].address, pool_id, 1, {"from": accounts[3]} + ) + + final_pool_supply = self.diamond_terminus.terminus_pool_supply(pool_id) + final_owner_balance = self.diamond_terminus.balance_of( + accounts[2].address, pool_id + ) + self.assertEqual(final_pool_supply, initial_pool_supply) + self.assertEqual(final_owner_balance, initial_owner_balance) + class TestCreatePoolV1(TestPoolOperations): def setUp(self): @@ -455,6 +605,29 @@ class TestCreatePoolV1(TestPoolOperations): self.assertEqual(final_pool_supply, initial_pool_supply - 1) self.assertEqual(final_owner_balance, initial_owner_balance - 1) + def test_burnable_pool_burn_as_authorized_third_party(self): + self.diamond_terminus.create_pool_v1(10, True, True, {"from": accounts[1]}) + pool_id = self.diamond_terminus.total_pools() + self.diamond_terminus.mint(accounts[2], pool_id, 1, b"", {"from": accounts[1]}) + + initial_pool_supply = self.diamond_terminus.terminus_pool_supply(pool_id) + initial_owner_balance = self.diamond_terminus.balance_of( + accounts[2].address, pool_id + ) + self.diamond_terminus.approve_for_pool( + pool_id, accounts[3].address, {"from": accounts[1]} + ) + self.diamond_terminus.burn( + accounts[2].address, pool_id, 1, {"from": accounts[3]} + ) + + final_pool_supply = self.diamond_terminus.terminus_pool_supply(pool_id) + final_owner_balance = self.diamond_terminus.balance_of( + accounts[2].address, pool_id + ) + self.assertEqual(final_pool_supply, initial_pool_supply - 1) + self.assertEqual(final_owner_balance, initial_owner_balance - 1) + def test_burnable_pool_burn_as_unauthorized_third_party(self): self.diamond_terminus.create_pool_v1(10, True, True, {"from": accounts[1]}) pool_id = self.diamond_terminus.total_pools() From 39baf061c229be798748ede34fcf86148061874c Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Wed, 22 Dec 2021 12:39:34 -0800 Subject: [PATCH 18/18] Deployed Terminus diamond to Mumbai testnet --- checklists/templates/terminus-deploy.md | 111 ++++++++++++++++ .../terminus-deploy-mumbai-20211222-2028.md | 121 ++++++++++++++++++ contracts/terminus/TerminusInitializer.sol | 4 +- 3 files changed, 234 insertions(+), 2 deletions(-) create mode 100644 checklists/templates/terminus-deploy.md create mode 100644 checklists/terminus-deploy-mumbai-20211222-2028.md diff --git a/checklists/templates/terminus-deploy.md b/checklists/templates/terminus-deploy.md new file mode 100644 index 0000000..aed0d10 --- /dev/null +++ b/checklists/templates/terminus-deploy.md @@ -0,0 +1,111 @@ +# Deploy the Terminus contract + +The Terminus contract is deployed as an EIP2535 Diamond proxy contract with a Terminus facet attached to it. + +This checklist describes how to deploy the contract. + +## Deployed addresses + +You will modify this section as you go through the checklist + +### Diamond addresses + +```json +``` + +### `TerminusInitializer` address + +``` +export TERMINUS_INITIALIZER_ADDRESS="" +``` + + +### `TerminusFacet` address + +``` +export TERMINUS_FACET_ADDRESS="" +``` + +## Environment variables + +- [ ] `export DAO_NETWORK=` +- [ ] `export DAO_OWNER=` +- [ ] `export DAO_OWNER_ADDRESS=$(jq -r .address $DAO_OWNER)` +- [ ] `export GAS_PRICE=" gwei"` +- [ ] `export CONFIRMATIONS=` +- [ ] `export TERMINUS_ADDRESSES=` + +## Deploy diamond proxy + +- [ ] Deploy diamond with all core facets + +```bash +dao core gogogo \ + --network $DAO_NETWORK \ + --sender $DAO_OWNER \ + --gas-price "$GAS_PRICE" \ + --confirmations $CONFIRMATIONS \ + --owner $DAO_OWNER_ADDRESS \ + --outfile $TERMINUS_ADDRESSES +``` + +- [ ] Store JSON output under `Deployed addresses / Diamond addresses` above. + +- [ ] Export diamond proxy address: `export TERMINUS_DIAMOND="$(jq -r .Diamond $TERMINUS_ADDRESSES)"` + + +## Deploy `TerminusInitializer` + +- [ ] Deploy `TerminusInitializer` contract + +```bash +dao moonstream-initializer deploy \ + --network $DAO_NETWORK \ + --sender $DAO_OWNER \ + --gas-price "$GAS_PRICE" \ + --confirmations $CONFIRMATIONS +``` + +- [ ] Export address of deployed contract as `export TERMINUS_INITIALIZER_ADDRESS=
` + +- [ ] Store address of deployed contract under `Deployed addresses / TerminusInitializer address` above + + +## Deploy `TerminusFacet` + +- [ ] Deploy `TerminusFacet` contract + +```bash +dao terminus deploy \ + --network $DAO_NETWORK \ + --sender $DAO_OWNER \ + --gas-price "$GAS_PRICE" \ + --confirmations $CONFIRMATIONS +``` + +- [ ] Export address of deployed contract as `export TERMINUS_FACET_ADDRESS=
` + +- [ ] Store address of deployed contract under `Deployed addresses / TerminusFacet address` above + +- [ ] Attach `TerminusFacet` to diamond: + +```bash +dao core facet-cut \ + --address $TERMINUS_DIAMOND \ + --network $DAO_NETWORK \ + --sender $DAO_OWNER \ + --gas-price "$GAS_PRICE" \ + --confirmations $CONFIRMATIONS \ + --facet-name TerminusFacet \ + --facet-address $TERMINUS_FACET_ADDRESS \ + --action add \ + --initializer-address $TERMINUS_INITIALIZER_ADDRESS +``` + +- [ ] Check the number of pools on the Terminus contract: `dao terminus total-pools --network $DAO_NETWORK --address $TERMINUS_DIAMOND` + +- [ ] Number of pools is `0` + +- [ ] Check the Terminus controller: `dao terminus terminus-controller --network $DAO_NETWORK --address $TERMINUS_DIAMOND` + +- [ ] Controller should be the same as `$DAO_OWNER_ADDRESS` diff --git a/checklists/terminus-deploy-mumbai-20211222-2028.md b/checklists/terminus-deploy-mumbai-20211222-2028.md new file mode 100644 index 0000000..41e614f --- /dev/null +++ b/checklists/terminus-deploy-mumbai-20211222-2028.md @@ -0,0 +1,121 @@ +# Deploy the Terminus contract + +The Terminus contract is deployed as an EIP2535 Diamond proxy contract with a Terminus facet attached to it. + +This checklist describes how to deploy the contract. + +## Deployed addresses + +You will modify this section as you go through the checklist + +### Diamond addresses + +```json +{ + "DiamondCutFacet": "0xda30781C3c8d4c81804E6Bf5c02D5E7898180dd7", + "Diamond": "0x040Cf7Ee9752936d8d280062a447eB53808EBc08", + "DiamondLoupeFacet": "0xEC5d886Bc5A7Fc31C76A5aB144c65C75AFa73Aea", + "OwnershipFacet": "0x2725E9FE8f5C97400d324C529e9ACBAd213E68b9", + "attached": [ + "DiamondLoupeFacet", + "OwnershipFacet" + ] +} +``` + +### `TerminusInitializer` address + +``` +export TERMINUS_INITIALIZER_ADDRESS="0xba71CB745C499D4A4f42Fd7aA40044b3b27Da6D4" +``` + + +### `TerminusFacet` address + +``` +export TERMINUS_FACET_ADDRESS="0x9784e26967779e62450Eb204077EF70B4c7A3612" +``` + +## Environment variables + +- [x] `export DAO_NETWORK=polygon-test` +- [x] `export DAO_OWNER=.secrets/dao-dev.json` +- [x] `export DAO_OWNER_ADDRESS=$(jq -r .address $DAO_OWNER)` +- [x] `export GAS_PRICE="35 gwei"` +- [x] `export CONFIRMATIONS=2` +- [x] `export TERMINUS_ADDRESSES=.secrets/terminus-mumbai-diamond.json` + +## Deploy diamond proxy + +- [x] Deploy diamond with all core facets + +```bash +dao core gogogo \ + --network $DAO_NETWORK \ + --sender $DAO_OWNER \ + --gas-price "$GAS_PRICE" \ + --confirmations $CONFIRMATIONS \ + --owner $DAO_OWNER_ADDRESS \ + --outfile $TERMINUS_ADDRESSES +``` + +- [x] Store JSON output under `Deployed addresses / Diamond addresses` above. + +- [x] Export diamond proxy address: `export TERMINUS_DIAMOND="$(jq -r .Diamond $TERMINUS_ADDRESSES)"` + + +## Deploy `TerminusInitializer` + +- [x] Deploy `TerminusInitializer` contract + +```bash +dao terminus-initializer deploy \ + --network $DAO_NETWORK \ + --sender $DAO_OWNER \ + --gas-price "$GAS_PRICE" \ + --confirmations $CONFIRMATIONS +``` + +- [x] Export address of deployed contract as `export TERMINUS_INITIALIZER_ADDRESS=0xba71CB745C499D4A4f42Fd7aA40044b3b27Da6D4` + +- [x] Store address of deployed contract under `Deployed addresses / TerminusInitializer address` above + + +## Deploy `TerminusFacet` + +- [x] Deploy `TerminusFacet` contract + +```bash +dao terminus deploy \ + --network $DAO_NETWORK \ + --sender $DAO_OWNER \ + --gas-price "$GAS_PRICE" \ + --confirmations $CONFIRMATIONS +``` + +- [x] Export address of deployed contract as `export TERMINUS_FACET_ADDRESS=0x9784e26967779e62450Eb204077EF70B4c7A3612` + +- [x] Store address of deployed contract under `Deployed addresses / TerminusFacet address` above + +- [x] Attach `TerminusFacet` to diamond: + +```bash +dao core facet-cut \ + --address $TERMINUS_DIAMOND \ + --network $DAO_NETWORK \ + --sender $DAO_OWNER \ + --gas-price "$GAS_PRICE" \ + --confirmations $CONFIRMATIONS \ + --facet-name TerminusFacet \ + --facet-address $TERMINUS_FACET_ADDRESS \ + --action add \ + --initializer-address $TERMINUS_INITIALIZER_ADDRESS +``` + +- [x] Check the number of pools on the Terminus contract: `dao terminus total-pools --network $DAO_NETWORK --address $TERMINUS_DIAMOND` + +- [x] Number of pools is `0` + +- [x] Check the Terminus controller: `dao terminus terminus-controller --network $DAO_NETWORK --address $TERMINUS_DIAMOND` + +- [x] Controller should be the same as `$DAO_OWNER_ADDRESS` diff --git a/contracts/terminus/TerminusInitializer.sol b/contracts/terminus/TerminusInitializer.sol index c72a338..befc99b 100644 --- a/contracts/terminus/TerminusInitializer.sol +++ b/contracts/terminus/TerminusInitializer.sol @@ -20,7 +20,7 @@ contract TerminusInitializer { ds.supportedInterfaces[type(IERC1155).interfaceId] = true; ds.supportedInterfaces[type(IERC1155MetadataURI).interfaceId] = true; - LibTerminus.TerminusStorage storage es = LibTerminus.terminusStorage(); - es.controller = msg.sender; + LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage(); + ts.controller = msg.sender; } }