Skip to content

Commit

Permalink
Merge pull request #27 from projectNEWM/adding-documentation
Browse files Browse the repository at this point in the history
Adding documentation
  • Loading branch information
logicalmechanism committed Mar 16, 2023
2 parents a37257c + 343830b commit 8c7bfd4
Show file tree
Hide file tree
Showing 10 changed files with 44 additions and 72 deletions.
26 changes: 19 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

A collection of smart contracts for Project NEWM.

# v0.2.x
# v1.0.0

The v0.2.x contract versions assume NEWM holds a special wallet that will regulate token minting and burning. In the future, this centralized signing mechanism will be replaced with a decentralized solution.
The v1.0.0 contract version assume NEWM holds a special wallet that will regulate the minting of tokenized tokens and minting and burning fractionalized tokens. A multisig regulates the burning of tokenized tokens.

### useful-funcs

Expand All @@ -14,18 +14,30 @@ https://github.com/logicalmechanism/useful-funcs

## Flow

An artist comes to NEWM wishing to tokenize a piece of art, digitalizing a real-world object onto the blockchain. The artist may decide to split the ownership rights of the tokenized object by fractionalization, splitting a tokeninzed object into 100,000,000 fractions. First, NEWM will create the initial tokenized object by minting an NFT on the official NEWM catalog policy id. At this point, the artist may keep the original tokenized version of their art or they may fractionalize the original NFT into a fungible token.
An artist comes to NEWM wishing to tokenize a piece of art, digitalizing a real-world object onto the blockchain. The artist may decide to split the ownership rights of the tokenized object by fractionalization, splitting a tokeninzed object into 100,000,000 fractions. First, NEWM will create the initial tokenized object by minting an NFT on an official NEWM catalog policy id. At this point, the artist may keep the original tokenized version of their art or they may fractionalize the original NFT into a fungible token.

The fractionalization of a token is the splitting of the ownership rights of the original tokenized art into 100 million pieces. The original tokenized art is locked into a smart contract and the resulting fractions are given to the artist. From here the artist may do as they wish with their fractions.
Fractionalization refers to the process of dividing the ownership of streaming rights of a tokenized piece of art into 100 million pieces, locking the tokenized token into a smart contract. The resulting fractions are given to the artist, who has the freedom to sell or trade them as they wish, or are sold on the NEWM marketplace with the proceeds going directly to the artist.

This entire process is reversible. At any point, an artist may obtain the original tokenized object by burning the 100 million fractional tokens.
The process is reversible. At any point, a wallet may obtain the original tokenized token by burning the 100 million fractional tokens.

## Tokenization

Each UTxO inside the tokenization contract holding the starter token and has a datum with correct information is a NEWM catalog. The datum has a token prefix like "NEWM_", "paintings_", or "songs_" and the current NFT counter. The contract behaves as a sequential NFT generator, minting tokens using the concatenation of the prefix with the current token counter. The sequence of NFTs will continue forever or at least until the size of the token name exceeds 32 characters. The resulting NFTs will have a token name like "NEWM_0" or "NEWM_414".
Each UTxO contained within the tokenization contract holding a starter token functions as a catalog for NEWM. The catalog includes a datum with a token prefix, such as "NEWM", "Paintings", or "Songs", as well as the current NFT counter. As a sequential NFT generator, the contract mints tokens by combining the prefix, an underscore, and the current token counter. This sequence of NFTs is infinite, or at least until the token name exceeds 32 characters. Therefore, the resulting NFTs will be named in the format "NEWM_0" or "NEWM_414", and so on.

## Fractionalization

Each UTxO inside the fractionalization contract is a unique fractionalization of a NEWM NFT. The fractionalized tokens will have the fractional NEWM policy id but will share the same name as the original tokenized object. The minting of the fractions will only occur if the original NEWM NFT is sent into the fractional contract to be locked. This means the fractionalized UTxO may only be unlocked if all 100 million fractions are burned together in a single transaction.
Within the fractionalization contract, each UTxO represents a distinct fractionalization of a NEWM tokenized token. These fractionalized tokens will have the fractional NEWM policy ID, but will retain the same name as the original tokenized object. To mint the fractions, the original NEWM NFT must first be sent into the fractional contract to be locked. As a result, the fractionalized UTxO can only be unlocked if all 100 million fractions are burned together in a single transaction.

## Building

Compiling the smart contracts is a simple as running the script `complete_build.sh` with a specific prefix.

```bash
./complete_build.sh NEWM
```

This script will populate the testing folder with the update-to-date contracts.

## Testing

The testing of the smart contracts are split into two steps located in the `test-suite` folder. The first step is starting the private testnet with `start_testnet_node.sh` script. The testnet is ready when the terminal displays `Testnet is ready for testing!`. The second step is running the python script `run_tests.py`. This will automatically run the test cases against the private testnet.
18 changes: 1 addition & 17 deletions locking-contract/app/locking-contract.hs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import qualified Data.ByteString.Lazy as BS
import qualified UsefulFuncs ( createBuiltinByteString )
import LockTokenizedNFTContract ( lockingContractScript, ScriptParameters(..) )
import Data.ByteString.Lazy ( ByteString )
-- import Data.ByteString.Lazy.Char8 ( unpack )

-- Define a type for the JSON data
data MyData = MyData
Expand All @@ -33,7 +32,6 @@ readJsonFile filePath = BS.readFile filePath
parseJson :: ByteString -> Maybe MyData
parseJson = decode

-- putStrLn $ "JSON Data: " ++ (unpack jsonData)
main :: IO ()
main = do
let filePath = "locking_info.json"
Expand All @@ -53,18 +51,4 @@ main = do
case result of
Left err -> print $ displayError err
Right () -> return ()
Nothing -> putStrLn "Failed to parse JSON data"




-- import Prelude
-- import Cardano.Api
-- import LockTokenizedNFTContract ( lockingContractScript )

-- main :: IO ()
-- main = do
-- result <- writeFileTextEnvelope "locking-contract.plutus" Nothing lockingContractScript
-- case result of
-- Left err -> print $ displayError err
-- Right () -> return ()
Nothing -> putStrLn "Failed to parse JSON data"
9 changes: 3 additions & 6 deletions locking-contract/src/LockTokenizedNFTContract.hs
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,9 @@ instance Eq CustomDatumType where
-------------------------------------------------------------------------------
-- | Create the redeemer type.
-------------------------------------------------------------------------------
data CustomRedeemerType = Lock |
Unlock
data CustomRedeemerType
= Lock -- Lock and fractionalize the NFT
| Unlock -- Unlock and solidify the fractions, releasing the nft
PlutusTx.makeIsDataIndexed ''CustomRedeemerType [ ( 'Lock, 0 )
, ( 'Unlock, 1 )
]
Expand All @@ -103,7 +104,6 @@ mkValidator ScriptParameters {..} datum redeemer context =
&& (traceIfFalse "Single Output Error" $ UsefulFuncs.isNOutputs contOutputs 0) -- single script output
&& (traceIfFalse "Burning Error" checkMintedAmount) -- burn the ft only
&& (traceIfFalse "Invalid Token Error" $ Value.valueOf validatingValue tPid (cdtTokenizedTn datum) == 1) -- Must contain the starter token

where
info :: PlutusV2.TxInfo
info = PlutusV2.scriptContextTxInfo context
Expand Down Expand Up @@ -150,7 +150,6 @@ mkValidator ScriptParameters {..} datum redeemer context =
-------------------------------------------------------------------------------
-- | Now we need to compile the Validator.
-------------------------------------------------------------------------------

wrappedValidator :: ScriptParameters -> BuiltinData -> BuiltinData -> BuiltinData -> ()
wrappedValidator s x y z = check (mkValidator s (PlutusV2.unsafeFromBuiltinData x) (PlutusV2.unsafeFromBuiltinData y) (PlutusV2.unsafeFromBuiltinData z))

Expand All @@ -159,8 +158,6 @@ validator sp = Plutonomy.optimizeUPLC $ Plutonomy.validatorToPlutus $ Plutonomy.
$$(PlutusTx.compile [|| wrappedValidator ||])
`PlutusTx.applyCode`
PlutusTx.liftCode sp
-- validator = Plutonomy.optimizeUPLCWith Plutonomy.aggressiveOptimizerOptions $ Plutonomy.validatorToPlutus $ Plutonomy.mkValidatorScript $$(PlutusTx.compile [|| wrappedValidator ||])


lockingContractScript :: ScriptParameters -> PlutusScript PlutusScriptV2
lockingContractScript = PlutusScriptSerialised . SBS.toShort . LBS.toStrict . serialise . validator
19 changes: 0 additions & 19 deletions minting-contract/app/minting-contract.hs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import qualified UsefulFuncs ( createBuiltinByteString )
import Data.ByteString.Lazy ( ByteString )
import MintFractionalizedTokenContract ( mintingPlutusScript, ScriptParameters(..) )


-- Define a type for the JSON data
data MyData = MyData
{ pkh :: [Integer]
Expand All @@ -33,7 +32,6 @@ readJsonFile filePath = BS.readFile filePath
parseJson :: ByteString -> Maybe MyData
parseJson = decode

-- putStrLn $ "JSON Data: " ++ (unpack jsonData)
main :: IO ()
main = do
let filePath = "minting_info.json"
Expand All @@ -54,20 +52,3 @@ main = do
Left err -> print $ displayError err
Right () -> return ()
Nothing -> putStrLn "Failed to parse JSON data"







-- import Prelude
-- import Cardano.Api
-- import MintFractionalizedTokenContract (mintingPlutusScript)

-- main :: IO ()
-- main = do
-- result <- writeFileTextEnvelope "minting-contract.plutus" Nothing mintingPlutusScript
-- case result of
-- Left err -> print $ displayError err
-- Right () -> return ()
10 changes: 7 additions & 3 deletions minting-contract/src/MintFractionalizedTokenContract.hs
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,9 @@ instance Eq CustomDatumType where
-------------------------------------------------------------------------------
{-# INLINABLE mkPolicy #-}
mkPolicy :: ScriptParameters -> BuiltinData -> PlutusV2.ScriptContext -> Bool
mkPolicy ScriptParameters {..} _ context = (traceIfFalse "Signing Tx Error" $ ContextsV2.txSignedBy info mainPkh)
&& (traceIfFalse "Mint/Burn/Datum Error" $ (checkMintedAmount && checkInputOutputDatum validatorHash) || (checkBurnedAmount && checkInputDatum validatorHash))
mkPolicy ScriptParameters {..} _ context
= (traceIfFalse "Signing Tx Error" $ ContextsV2.txSignedBy info mainPkh)
&& (traceIfFalse "Mint/Burn/Datum Error" $ (checkMintedAmount && checkInputOutputDatum validatorHash) || (checkBurnedAmount && checkInputDatum validatorHash))
where
info :: PlutusV2.TxInfo
info = PlutusV2.scriptContextTxInfo context
Expand All @@ -89,9 +90,11 @@ mkPolicy ScriptParameters {..} _ context = (traceIfFalse "Signing Tx Error"
checkPolicyId :: PlutusV2.CurrencySymbol -> Bool
checkPolicyId cs = cs == ContextsV2.ownCurrencySymbol context

-- must mint 100 million fractions
mintAmount :: Integer -> Bool
mintAmount amt = amt == (100_000_000 :: Integer)

-- must burn 100 million fractions
burnAmount :: Integer -> Bool
burnAmount amt = amt == (-100_000_000 :: Integer)

Expand Down Expand Up @@ -174,4 +177,5 @@ policy sp = PlutusV2.mkMintingPolicyScript $
PlutusTx.liftCode sp

mintingPlutusScript :: ScriptParameters -> PlutusScript PlutusScriptV2
mintingPlutusScript sp = PlutusScriptSerialised . SBS.toShort $ LBS.toStrict $ serialise $ Plutonomy.optimizeUPLC $ PlutusV2.Validator $ PlutusV2.unMintingPolicyScript (policy sp)
mintingPlutusScript sp = PlutusScriptSerialised . SBS.toShort $ LBS.toStrict $ serialise $
Plutonomy.optimizeUPLC $ PlutusV2.Validator $ PlutusV2.unMintingPolicyScript (policy sp)
2 changes: 0 additions & 2 deletions nft-locking-contract/app/nft-locking-contract.hs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import qualified Data.ByteString.Lazy as BS
import qualified UsefulFuncs ( createBuiltinByteString )
import LockStarterNFTContract ( lockingContractScript, ScriptParameters(..) )
import Data.ByteString.Lazy ( ByteString )
-- import Data.ByteString.Lazy.Char8 ( unpack )

-- Define a type for the JSON data
data MyData = MyData
Expand All @@ -37,7 +36,6 @@ readJsonFile filePath = BS.readFile filePath
parseJson :: ByteString -> Maybe MyData
parseJson = decode

-- putStrLn $ "JSON Data: " ++ (unpack jsonData)
main :: IO ()
main = do
let filePath = "nft_locking_info.json"
Expand Down
13 changes: 5 additions & 8 deletions nft-locking-contract/src/LockStarterNFTContract.hs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ PlutusTx.makeLift ''ScriptParameters
-------------------------------------------------------------------------------
nftName :: PlutusV2.BuiltinByteString -> Integer -> PlutusV2.BuiltinByteString
nftName prefix num = prefix <> "_" <> UsefulFuncs.integerAsByteString num
-- nftName prefix num = prefix <> UsefulFuncs.integerAsByteString num
-------------------------------------------------------------------------------
-- | Create the datum parameters data object.
-------------------------------------------------------------------------------
Expand Down Expand Up @@ -106,8 +105,9 @@ instance Eq CustomDatumType where
-------------------------------------------------------------------------------
-- | Create the redeemer types.
-------------------------------------------------------------------------------
data CustomRedeemerType = Mint |
Burn
data CustomRedeemerType
= Mint -- Mint the next sequential tokenization NFT
| Burn -- Burn a tokenization NFT
PlutusTx.makeIsDataIndexed ''CustomRedeemerType [ ( 'Mint, 0 )
, ( 'Burn, 1 )
]
Expand All @@ -133,7 +133,6 @@ mkValidator ScriptParameters {..} datum redeemer context =
&& (traceIfFalse "NFT Burning Error" checkBurnedAmount) -- Correct token burn
&& (traceIfFalse "Invalid Datum Error" $ isContDatumCorrect contOutputs validatingValue redeemer) -- Value is continuing and the datum is correct
&& (traceIfFalse "Invalid Starter Tkn" $ Value.valueOf validatingValue starterPid starterTkn == 1) -- Must contain the starter token

where
info :: PlutusV2.TxInfo
info = PlutusV2.scriptContextTxInfo context
Expand All @@ -144,7 +143,6 @@ mkValidator ScriptParameters {..} datum redeemer context =
txInputs :: [PlutusV2.TxInInfo]
txInputs = PlutusV2.txInfoInputs info

-- | This is the currently validating value from the UTxO being spent in this tx.
validatingValue :: PlutusV2.Value
validatingValue =
case ContextsV2.findOwnInput context of
Expand All @@ -164,8 +162,8 @@ mkValidator ScriptParameters {..} datum redeemer context =
checkBurnedAmount :: Bool
checkBurnedAmount =
case Value.flattenValue (PlutusV2.txInfoMint info) of
[(cs, _, amt)] -> (cs == cdtNewmPid datum) && (amt == (-1 :: Integer))
_ -> traceError "Bad Burning" -- error on bad burns
[(cs, _, amt)] -> (cs == cdtNewmPid datum) && (amt == (-1 :: Integer)) -- burn conditions
_ -> traceError "Bad Burning" -- error on bad burns

-- | Check if the continue output datum is of the correct form.
isContDatumCorrect :: [PlutusV2.TxOut] -> PlutusV2.Value -> CustomRedeemerType -> Bool
Expand Down Expand Up @@ -195,7 +193,6 @@ validator sp = Plutonomy.optimizeUPLC $ Plutonomy.validatorToPlutus $ Plutonomy.
$$(PlutusTx.compile [|| wrappedValidator ||])
`PlutusTx.applyCode`
PlutusTx.liftCode sp
-- validator = Plutonomy.optimizeUPLCWith Plutonomy.aggressiveOptimizerOptions $ Plutonomy.validatorToPlutus $ Plutonomy.mkValidatorScript $$(PlutusTx.compile [|| wrappedValidator ||])

lockingContractScript :: ScriptParameters -> PlutusScript PlutusScriptV2
lockingContractScript = PlutusScriptSerialised . SBS.toShort . LBS.toStrict . serialise . validator
1 change: 0 additions & 1 deletion nft-minting-contract/app/nft-minting-contract.hs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ readJsonFile filePath = BS.readFile filePath
parseJson :: ByteString -> Maybe MyData
parseJson = decode

-- putStrLn $ "JSON Data: " ++ (unpack jsonData)
main :: IO ()
main = do
let filePath = "nft_minting_info.json"
Expand Down

0 comments on commit 8c7bfd4

Please sign in to comment.