Skip to content

Instantly share code, notes, and snippets.

@gclaramunt
Created March 12, 2021 03:03
Show Gist options
  • Save gclaramunt/5e98b848cf45c449116086b764092893 to your computer and use it in GitHub Desktop.
Save gclaramunt/5e98b848cf45c449116086b764092893 to your computer and use it in GitHub Desktop.
Plutus Playground Smart Contract
import Control.Monad (void)
import qualified Data.ByteString.Char8 as C
import Language.Plutus.Contract
import qualified Language.PlutusTx as PlutusTx
import Language.PlutusTx.Prelude hiding (pure, (<$>))
import Ledger (Address (..), Validator, ValidatorCtx(..), TxInfo(..), TxOut (..), TxOutTx (..), Value, scriptAddress)
import Plutus.V1.Ledger.Scripts (Datum (..), DatumHash, MonetaryPolicyHash, Redeemer, ValidatorHash, validatorHash)
import qualified Ledger.Constraints as Constraints
import qualified Ledger.Contexts as Validation
import qualified Ledger.Typed.Scripts as Scripts
import qualified Ledger.Value as Value
import Playground.Contract
import qualified Prelude
import Data.Map (elems)
------------------------------------------------------------
newtype HashedString = HashedString ByteString deriving newtype PlutusTx.IsData
PlutusTx.makeLift ''HashedString
newtype ClearString = ClearString ByteString deriving newtype PlutusTx.IsData
PlutusTx.makeLift ''ClearString
data BountyConfig = BountyConfig
{ target :: Integer
, lockScript :: ValidatorHash
} deriving Generic
PlutusTx.makeLift ''BountyConfig
-- | Datum and redeemer parameter types for math bounty
data MathBounty
instance Scripts.ScriptType MathBounty where
type instance RedeemerType MathBounty = (Value,Integer)
type instance DatumType MathBounty = ()
-- | Datum and redeemer parameter types for the pin lock
data PinLock
instance Scripts.ScriptType PinLock where
type instance RedeemerType PinLock = ClearString
type instance DatumType PinLock = HashedString
-- create a data script for unlocking by hashing the string
-- and lifting the hash to its on-chain representation
hashString :: String -> HashedString
hashString = HashedString . sha2_256 . C.pack
-- create a redeemer script for unlocking by lifting the
-- string to its on-chain representation
clearString :: String -> ClearString
clearString = ClearString . C.pack
-- | Spending validators
-- (which gets lifted to its on-chain representation).
-- | The math problem validator: the square of the proposed value is the expected solution
validateSolution :: BountyConfig -> () -> (Value,Integer) -> ValidatorCtx -> Bool
validateSolution BountyConfig{ target, lockScript} () (amountToLock, x) ValidatorCtx{valCtxTxInfo=txInfo} =
let
toPinlockValidator = Validation.valueLockedBy txInfo lockScript
in
x*x == target && toPinlockValidator `Value.leq` amountToLock
-- | The pinlock validator
validatePinLock :: HashedString -> ClearString -> ValidatorCtx -> Bool
validatePinLock (HashedString lockPin) (ClearString unlockPin) _ = lockPin == sha2_256 unlockPin
-- | The script instance is the compiled pinlock validator (ready to go onto the chain)
pinlockInstance :: Scripts.ScriptInstance PinLock
pinlockInstance = Scripts.validator @PinLock
$$(PlutusTx.compile [|| validatePinLock ||])
$$(PlutusTx.compile [|| wrap ||]) where
wrap = Scripts.wrapValidator @HashedString @ClearString
-- | The script instance is the compiled bounty validator (ready to go onto the chain)
bountyInstance :: BountyConfig -> Scripts.ScriptInstance MathBounty
bountyInstance params = Scripts.validator @MathBounty
($$(PlutusTx.compile [|| validateSolution ||]) `PlutusTx.applyCode` PlutusTx.liftCode params)
$$(PlutusTx.compile [|| wrap ||]) where
wrap = Scripts.wrapValidator @() @(Value, Integer)
-- | The address of the bounty script (the hash of its validator script)
bountyAddress :: BountyConfig -> Address
bountyAddress config = Ledger.scriptAddress (Scripts.validatorScript (bountyInstance config))
-- | The address of the pin lock script (the hash of its validator script)
pinlockAddress :: Address
pinlockAddress = Ledger.scriptAddress (Scripts.validatorScript pinlockInstance)
-- | Endpoints
-- | The schema of the contract, with one endpoint to publish the problem with a bounty and another to submit solutions
type MathBountyWithLockSchema =
BlockchainActions
.\/ Endpoint "bounty" BountyParams
.\/ Endpoint "solveAndLock" SolutionLockParams
.\/ Endpoint "unlock" UnlockParams
-- | Parameters for the "bounty" endpoint
data BountyParams = BountyParams
{ problem_target :: Integer
, amount :: Value
}
deriving stock (Prelude.Eq, Prelude.Show, Generic)
deriving anyclass (FromJSON, ToJSON, IotsType, ToSchema, ToArgument)
-- | Parameters for the "solution" endpoint
data SolutionLockParams = SolutionParams
{ goal :: Integer
, proposed_solution :: Integer
, lockPin :: String
, amountToLock :: Value
}
deriving stock (Prelude.Eq, Prelude.Show, Generic)
deriving anyclass (FromJSON, ToJSON, IotsType, ToSchema, ToArgument)
-- | Parameters for the "unlock" endpoint
newtype UnlockParams = UnlockParams
{ unlockPin :: String
}
deriving stock (Prelude.Eq, Prelude.Show, Generic)
deriving anyclass (FromJSON, ToJSON, IotsType, ToSchema, ToArgument)
-- | The "bounty" contract endpoint.
bounty :: AsContractError e => Contract MathBountyWithLockSchema e ()
bounty = do
BountyParams target amt <- endpoint @"bounty" @BountyParams
let ScriptAddress pinLockVHash = pinlockAddress
let tx = Constraints.mustPayToTheScript () amt
void (submitTxConstraints (bountyInstance BountyConfig{ target = target, lockScript= pinLockVHash }) tx)
-- | The "solution" contract endpoint.
solution :: AsContractError e => Contract MathBountyWithLockSchema e ()
solution = do
SolutionParams target theProposal lockPin amountToLock <- endpoint @"solveAndLock" @SolutionLockParams
let ScriptAddress pinLockVHash = pinlockAddress
unspentOutputs <- utxoAt (bountyAddress BountyConfig{ target = target, lockScript= pinLockVHash })
-- calculate the total amount in the bounty
let totalBounty = foldl1 (<>) $ map (txOutValue.txOutTxOut) (elems unspentOutputs)
let txCollect = (collectFromScript unspentOutputs (totalBounty, theProposal))
let amt = if (amountToLock `Value.geq` totalBounty) then totalBounty else amountToLock
-- lock the amount
let ScriptAddress pinLockVHash = pinlockAddress
let txLock = Constraints.mustPayToOtherScript pinLockVHash (Datum $ PlutusTx.toData (hashString lockPin)) amt
let tx = txCollect <> txLock
void (submitTxConstraintsSpending (bountyInstance BountyConfig{ target = target, lockScript= pinLockVHash }) unspentOutputs tx)
-- | The "unlock" contract endpoint. See note [Contract endpoints]
unlock :: AsContractError e => Contract MathBountyWithLockSchema e ()
unlock = do
UnlockParams unlockPin <- endpoint @"unlock" @UnlockParams
unspentOutputs <- utxoAt pinlockAddress
let redeemer = clearString unlockPin
tx = collectFromScript unspentOutputs redeemer
void (submitTxConstraintsSpending pinlockInstance unspentOutputs tx)
-- | join all endpoints.
endpoints :: AsContractError e => Contract MathBountyWithLockSchema e ()
endpoints = bounty `select` solution `select` unlock
mkSchemaDefinitions ''MathBountyWithLockSchema
$(mkKnownCurrencies [])
[0,[{"simulationWallets":[{"simulatorWalletWallet":{"getWallet":1},"simulatorWalletBalance":{"getValue":[[{"unCurrencySymbol":""},[[{"unTokenName":""},10]]]]}},{"simulatorWalletWallet":{"getWallet":2},"simulatorWalletBalance":{"getValue":[[{"unCurrencySymbol":""},[[{"unTokenName":""},10]]]]}}],"simulationName":"Simulation 1","simulationId":1,"simulationActions":[]}]]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment