Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add EIP: Purpose bound money #7292

Closed
wants to merge 19 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
699 changes: 699 additions & 0 deletions EIPS/eip-7291.md

Large diffs are not rendered by default.

Binary file added assets/eip-7291/MAS-Project-Orchid.pdf
Binary file not shown.
Binary file added assets/eip-7291/PBM-Technical-Whitepaper.pdf
Binary file not shown.
59 changes: 59 additions & 0 deletions assets/eip-7291/README.md
@@ -0,0 +1,59 @@
# PBM Solidity implementation

## Description

We provide a list of sample PBM implementation for reference.

### Provided Contracts and Tests

<!-- TBD: Explain the folder structure -->

- `contracts/preloaded-pbm/XXXX.sol` - PBMRC1 implementation contract to demonstrate preloaded PBMs
- `contracts/non-preloaded-pbm/XXXX.sol` - Interface contract
<!-- - `contracts/attest-unlock-pbm/XXXX.sol` - contract to demonstrate a 3rd party attestation to allow unwrap of a PBM -->
- `contracts/XXXX.sol` - Interface contract
- `contracts/ERC20.sol` - ERC20 token contract for unit tests
- `test/XXXXX.js` - Unit tests for livecycle of the PBM implementation

### Used javascript based testing libraries for solidity

<!-- TBD: Fill this up with libraries used -->

- `hardhat`: hardhat allows for testing of contracts with JavaScript via Mocha as the test runner
- `chai`: Chai is an assertion library and provides functions like expect.
- `ethers`: This is a popular Ethereum client library. It allows you to interface with blockchains that implement the Ethereum API.

### Compile and run tests with hardhat

<!-- TBD: Improve this with nix file -->

We provide the essential steps to compile the contracts and run provided unit tests
Check that you have the latest version of npm and node via `npm -version` and `node -v` (should be a LTS version for hardhat support)

1. Check out project
2. Go to folder and initialise a new npm project: `npm init -y`. A basic `package.json` file should occur
3. Install Hardhat as local solidity dev environment: `npx hardhat`
4. Select following option: Create an empty hardhat.config.js
5. Install Hardhat as a development dependency: `npm install --save-dev hardhat`
6. Install further testing dependencies:
`npm install --save-dev @nomiclabs/hardhat-waffle @nomiclabs/hardhat-ethers ethereum-waffle chai ethers solidity-coverage`
7. Install open zeppelin contracts: `npm install @openzeppelin/contracts`
8. add plugins to hardhat.config.ts:

```
require("@nomiclabs/hardhat-waffle");
require('solidity-coverage');
```

9. Adding commands to `package.json`:

```
"scripts": {
"build": "hardhat compile",
"test:light": "hardhat test",
"test": "hardhat coverage"
},
```

9. run `npm run build`
10. run `npm run test`
Empty file.
17 changes: 17 additions & 0 deletions assets/eip-7291/contracts/ERC1155Metadata_URI.sol
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

/**
* @notice
* @dev The ERC-165 identifier for this interface is 0x0e89341c.
*/
interface ERC1155Metadata_URI {
/**
@notice A distinct Uniform Resource Identifier (URI) for a given token.
@dev URIs are defined in RFC 3986.
The URI MUST point to a JSON file that conforms to the "ERC-1155 Metadata URI JSON Schema".
@return URI string
*/
function uri(uint256 _id) external view returns (string memory);
}

19 changes: 19 additions & 0 deletions assets/eip-7291/contracts/ERC173.sol
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

/// @title ERC-173 Contract Ownership Standard
/// Note: the ERC-165 identifier for this interface is 0x7f5828d0
/* is ERC165 */
interface IERC173 {
/// @dev This emits when ownership of a contract changes.
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

/// @notice Get the address of the owner
/// @return owner_ The address of the owner.
function owner() external view returns (address owner_);

/// @notice Set the address of the new owner of the contract
/// @dev Set _newOwner to address(0) to renounce any ownership.
/// @param _newOwner The address of the new owner of the contract
function transferOwnership(address _newOwner) external;
}
221 changes: 221 additions & 0 deletions assets/eip-7291/contracts/IPBMRC1.sol

Large diffs are not rendered by default.

79 changes: 79 additions & 0 deletions assets/eip-7291/contracts/IPBMRC1_TokenManager.sol
@@ -0,0 +1,79 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

abstract contract IPBMRC1_TokenManager {
/// @dev Mapping of each ERC-1155 tokenId to its corresponding PBM Token details.
mapping (uint256 => PBMToken) internal tokenTypes ;

/// @notice A PBM token MUST include compulsory state variables (name, faceValue, expiry, and uri) to adhere to this standard.
/// @dev Represents all the details corresponding to a PBM tokenId.
struct PBMToken {
// Name of the token.
string name;

// Value of the underlying wrapped ERC20-compatible sovToken. Additional information on the `faceValue` can be specified by
// adding the optional variables: `currencySymbol` or `tokenSymbol` as indicated below
uint256 faceValue;

// Time after which the token will be rendered useless (expressed in Unix Epoch time).
uint256 expiry;

// Metadata URI for ERC-1155 display purposes.
string uri;

// OPTIONAL: Indicates if the PBM token can be transferred to a non merchant/redeemer wallet.
bool isTransferable;

// OPTIONAL: Determines whether the PBM will be burned or revoked upon expiry, under certain predefined conditions, or at the owner's discretion.
bool burnable;

// OPTIONAL: Number of decimal places for the token.
uint8 decimals;

// OPTIONAL: The address of the creator of this PBM type on this smart contract. This field is optional because the creator is msg.sender by default.
address creator;

// OPTIONAL: The smart contract address of the sovToken.
address tokenAddress;

// OPTIONAL: The running balance of the PBM Token type that has been minted.
uint256 totalSupply;

// OPTIONAL: An ISO4217 three-character alphabetic code may be needed for the faceValue in multicurrency PBM use cases.
string currencySymbol;

// OPTIONAL: An abbreviation for the PBM token name may be assigned.
string tokenSymbol;

// Add other optional state variables below...
}

/// @notice Creates a new PBM Token type with the provided data.
/// @dev The caller of createPBMTokenType shall be responsible for setting the creator address.
/// Example of uri can be found in [`sample-uri`](../assets/eip-7291/sample-uri/stx-10-static)
/// Must emit {NewPBMTypeCreated}
/// @param _name Name of the token.
/// @param _faceValue Value of the underlying wrapped ERC20-compatible sovToken.
/// @param _tokenExpiry Time after which the token will be rendered useless (expressed in Unix Epoch time).
/// @param _tokenURI Metadata URI for ERC-1155 display purposes
function createPBMTokenType(
string memory _name,
uint256 _faceValue,
uint256 _tokenExpiry,
string memory _tokenURI
) external virtual returns (uint256 tokenId_);

/// @notice Retrieves the details of a PBM Token type given its tokenId.
/// @dev This function fetches the PBMToken struct associated with the tokenId and returns it.
/// @param tokenId The identifier of the PBM token type.
/// @return pbmToken_ A PBMToken struct containing all the details of the specified PBM token type.
function getTokenDetails(uint256 tokenId) external virtual view returns(PBMToken memory pbmToken_);

/// @notice Emitted when a new Purpose-Bound Token (PBM) type is created within the contract.
/// @param tokenId The unique identifier for the newly created PBM token type.
/// @param tokenName A human-readable string representing the name of the newly created PBM token type.
/// @param amount The initial supply of the newly created PBM token type.
/// @param expiry The timestamp at which the newly created PBM token type will expire.
/// @param creator The address of the account that created the new PBM token type.
event NewPBMTypeCreated(uint256 tokenId, string tokenName, uint256 amount, uint256 expiry, address creator);
}
34 changes: 34 additions & 0 deletions assets/eip-7291/contracts/IPBMRC1_TokenReceiver.sol
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

/// @notice Smart contracts MUST implement the ERC-165 `supportsInterface` function and signify support for the `PBMRC1_TokenReceiver` interface to accept callbacks.
/// It is optional for a receiving smart contract to implement the `PBMRC1_TokenReceiver` interface
/// @dev WARNING: Reentrancy guard procedure, Non delegate call, or the check-effects-interaction pattern must be adhere to when calling an external smart contract.
/// The interface functions MUST only be called at the end of the `unwrap` function.
interface PBMRC1_TokenReceiver {
/**
@notice Handles the callback from a PBM smart contract upon unwrapping
@dev An PBM smart contract MUST call this function on the token recipient contract, at the end of a `unwrap` if the
receiver smart contract supports type(PBMRC1_TokenReceiver).interfaceId
@param _operator The address which initiated the transfer (either the address which previously owned the token or the address authorised to make transfers on the owner's behalf) (i.e. msg.sender)
@param _from The address which previously owned the token
@param _id The ID of the token being unwrapped
@param _value The amount of tokens being transferred
@param _data Additional data with no specified format
@return `bytes4(keccak256("onPBMRC1Unwrap(address,address,uint256,uint256,bytes)"))`
*/
function onPBMRC1Unwrap(address _operator, address _from, uint256 _id, uint256 _value, bytes calldata _data) external returns(bytes4);

/**
@notice Handles the callback from a PBM smart contract upon unwrapping a batch of tokens
@dev An PBM smart contract MUST call this function on the token recipient contract, at the end of a `unwrap` if the
receiver smart contract supports type(PBMRC1_TokenReceiver).interfaceId
@param _operator The address which initiated the transfer (either the address which previously owned the token or the address authorised to make transfers on the owner's behalf) (i.e. msg.sender)
@param _from The address which previously owned the token
@param _ids IDs of the tokend being unwrapped
@param _values The amount of tokens being transferred
@param _data Additional data with no specified format
@return `bytes4(keccak256("onPBMRC1BatchUnwrap(address,address,uint256,uint256,bytes)"))`
*/
function onPBMRC1BatchUnwrap(address _operator, address _from, uint256[] calldata _ids, uint256[] calldata _values, bytes calldata _data) external returns(bytes4);
}
57 changes: 57 additions & 0 deletions assets/eip-7291/contracts/IPBMRC2.sol
@@ -0,0 +1,57 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

/**
* @dev This interface extends IPBMRC1, adding functions for working with non-preloaded PBMs.
* Non-preloaded PBMs are minted as empty containers without any underlying tokens of value,
* allowing the loading of the underlying token to happen at a later stage.
*/
interface PBMRC2_NonPreloadedPBM is IPBMRC1 {

/// @notice This function extends IPBMRC1 to mint PBM tokens as empty containers without underlying tokens of value.
/// @dev The loading of the underlying token of value can be done by calling the `load` function. The function parameters should be identical to IPBMRC1
function safeMint(address receiver, uint256 tokenId, uint256 amount, bytes calldata data) external;

/// @notice This function extends IPBMRC1 to mint PBM tokens as empty containers without underlying tokens of value.
/// @dev The loading of the underlying token of value can be done by calling the `load` function. The function parameters should be identical to IPBMRC1
function safeMintBatch(address to, uint256[] calldata ids, uint256[] calldata amounts, bytes calldata data) external;

/// @notice Wrap an amount of sovTokens into the PBM
/// @dev function will pull sovTokens from msg.sender
/// Approval must be given to the PBM smart contract in order to for the pbm to pull money from msg.sender
/// underlying data structure must record how much the msg.sender has been loaded into the PBM.
/// Emits {TokenLoad} event.
/// @param amount The amount of sovTokens to be loaded
function load(uint256 amount) external;

/// @notice Retrieves the balance of the underlying sovToken associated with a specific PBM token type and user address.
/// This function provides a way to check the amount of the underlying token that a user has loaded into a particular PBM token.
/// @param user The address of the user whose underlying token balance is being queried.
/// @return The balance of the underlying sovToken associated with the specified PBM token type and user address.
function underlyingBalanceOf(address user) external view returns (uint256);

/// @notice Unloads all of the underlying token belonging to the caller from the PBM smart contract.
/// @dev The underlying token that belongs to the caller (msg.sender) will be removed and transferred
/// back to the caller.
/// Emits {TokenUnload} event.
/// @param amount The quantity of the corresponding tokens to be unloaded.
/// Amount should not exceed the amount that the caller has originally loaded into the PBM smart contract.
function unload(uint256 amount) external;

/// @notice Emitted when an underlying token is loaded into a PBM
/// @param caller Address by which sovToken is taken from.
/// @param to Address by which the token is loaded and assigned to
/// @param amount The quantity of tokens to be loaded
/// @param sovToken The address of the underlying sovToken.
/// @param sovTokenValue The amount of underlying sovTokens loaded
event TokenLoad(address caller, address to, uint256 amount, address sovToken, uint256 sovTokenValue);

/// @notice Emitted when an underlying token is unloaded from a PBM.
/// This event indicates the process of releasing the underlying token from the PBM smart contract.
/// @param caller The address initiating the token unloading process.
/// @param from The address from which the token is being unloaded and removed from.
/// @param amount The quantity of the corresponding unloaded tokens.
/// @param sovToken The address of the underlying sovToken.
/// @param sovTokenValue The amount of unloaded underlying sovTokens transferred.
event TokenUnload(address caller, address from, uint256 amount, address sovToken, uint256 sovTokenValue);
}
29 changes: 29 additions & 0 deletions assets/eip-7291/contracts/IPBM_AddressList.sol
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

/// @title PBM Address list Interface.
/// @notice The PBM address list stores and manages whitelisted merchants/redeemers and blacklisted address for the PBMs
interface IPBMAddressList {

/// @notice Checks if the address is one of the blacklisted addresses
/// @param _address The address to query
/// @return bool_ True if address is blacklisted, else false
function isBlacklisted(address _address) external returns (bool bool_) ;

/// @notice Checks if the address is one of the whitelisted merchant/redeemer addresses
/// @param _address The address to query
/// @return bool_ True if the address is in merchant/redeemer whitelist and is NOT a blacklisted address, otherwise false.
function isMerchant(address _address) external returns (bool bool_) ;

/// @notice Event emitted when the Merchant/Redeemer List is edited
/// @param action Tags "add" or "remove" for action type
/// @param addresses An array of merchant wallet addresses that was just added or removed from Merchant/Redeemer whitelist
/// @param metadata Optional comments or notes about the added or removed addresses.
event MerchantList(string action, address[] addresses, string metadata);

/// @notice Event emitted when the Blacklist is edited
/// @param action Tags "add" or "remove" for action type
/// @param addresses An array of wallet addresses that was just added or removed from address blacklist
/// @param metadata Optional comments or notes about the added or removed addresses.
event Blacklist(string action, address[] addresses, string metadata);
}
43 changes: 43 additions & 0 deletions assets/eip-7291/contracts/PBMRC1.sol
@@ -0,0 +1,43 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

/**
* @dev The ERC-165 identifier for this interface is 0x0e89341c.
*/
interface ERC1155Metadata_URI {
/**
@notice A distinct Uniform Resource Identifier (URI) for a given token.
@dev URIs are defined in RFC 3986.
The URI MUST point to a JSON file that conforms to the "ERC-1155 Metadata URI JSON Schema".
@return URI string
*/
function uri(uint256 _id) external view returns (string memory);
}

/// @title ERC-173 Contract Ownership Standard
/// Note: the ERC-165 identifier for this interface is 0x7f5828d0
interface IERC173 {
/// @dev This emits when ownership of a contract changes.
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

/// @notice Get the address of the owner
/// @return owner_ The address of the owner.
function owner() external view returns (address owner_);

/// @notice Set the address of the new owner of the contract
/// @dev Set _newOwner to address(0) to renounce any ownership.
/// @param _newOwner The address of the new owner of the contract
function transferOwnership(address _newOwner) external;
}

/// The EIP-165 identifier of this interface is 0xf4cedd5a
interface IERC5679Ext1155 {
function safeMint(address _to, uint256 _id, uint256 _amount, bytes calldata _data) external;
function safeMintBatch(address _to, uint256[] calldata ids, uint256[] calldata amounts, bytes calldata _data) external;
function burn(address _from, uint256 _id, uint256 _amount, bytes[] calldata _data) external;
function burnBatch(address _from, uint256[] calldata ids, uint256[] calldata amounts, bytes calldata _data) external;
}

abstract contract PBMRC1 is IPBMRC1, ERC1155Metadata_URI {
// Implementation code will implement this abstract class
}
15 changes: 15 additions & 0 deletions assets/eip-7291/sample-uri/expiry-static
@@ -0,0 +1,15 @@
{
"name": "SFF expired voucher",
"description": "Collectible",
"image": "https://gateway.pinata.cloud/ipfs/QmdKf2FNDuQoxWT3rrocvUhbbuXuUdTdHy17mzVXHYNUV9",
"attributes": [
{
"trait_type": "Value",
"value": "0"
},
{
"trait_type": "Expired",
"value": "True"
}
]
}
15 changes: 15 additions & 0 deletions assets/eip-7291/sample-uri/grab-10-static
@@ -0,0 +1,15 @@
{
"name": "Grab-0.01",
"description": "$0.01 SGD test voucher",
"image": "https://gateway.pinata.cloud/ipfs/QmTb6Rqib7sUQ2HL7cUed4m9KYK5Gw43MH2CUXNvGeC9gM",
"attributes": [
{
"trait_type": "Value",
"value": "0.01"
},
{
"trait_type": "Expired",
"value": "False"
}
]
}
15 changes: 15 additions & 0 deletions assets/eip-7291/sample-uri/stx-10-static
@@ -0,0 +1,15 @@
{
"name": "StraitsX-0.01",
"description": "$0.01 SGD test voucher",
"image": "https://gateway.pinata.cloud/ipfs/Qmf69TDf3JccGvF9BB4rp4b934QYD8uiffXzfWMgxK3ozk",
"attributes": [
{
"trait_type": "Value",
"value": "0.01"
},
{
"trait_type": "Expired",
"value": "False"
}
]
}