Created
March 12, 2021 03:03
-
-
Save gclaramunt/5e98b848cf45c449116086b764092893 to your computer and use it in GitHub Desktop.
Plutus Playground Smart Contract
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 []) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[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