scalus
scalus copied to clipboard
Support of object private methods called from overrided inline functions.
When private method is called from overrided inline function (like spend), then compiler gednerate calls to bridge method MyObject$inline$method inside inline$method. Need to handle this during code generation..
Just a sample repro:
object Vault extends Validator {
inline override def spend(
d: prelude.Option[Data],
redeemer: Data,
tx: TxInfo,
ownRef: TxOutRef
): Unit = {
val datum = d.get.to[Datum]
redeemer.to[Redeemer] match {
case Deposit => deposit(tx, ownRef, datum)
case InitiateWithdrawal => initiateWithdrawal(tx, ownRef, datum)
case FinalizeWithdrawal => finalize(tx, ownRef, datum)
case Cancel => cancel(tx, ownRef, datum)
}
}
private def deposit(tx: TxInfo, ownRef: TxOutRef, datum: Datum) = {
val ownInput = tx.findOwnInput(ownRef).getOrFail(OwnInputNotFound)
val out = getVaultOutput(tx, ownRef)
requireSameOwner(out, datum)
requireOutputToOwnAddress(ownInput, out, WrongDepositDestination)
val value = out.value
require(value.withoutLovelace.isZero, CannotAddTokens)
require(value.getLovelace > ownInput.resolved.value.getLovelace, AdaNotConserved)
requireEntireVaultIsSpent(datum, ownInput.resolved)
val newDatum = getVaultDatum(out)
require(newDatum.amount == value.getLovelace, VaultAmountChanged)
require(newDatum.waitTime == datum.waitTime, WaitTimeChanged)
require(
newDatum.finalizationDeadline == datum.finalizationDeadline,
FinalizationDeadlineChanged
)
}
private def initiateWithdrawal(tx: TxInfo, ownRef: TxOutRef, datum: Datum) = {
require(
datum.state.isIdle,
WithdrawalAlreadyPending
)
val ownInput = tx.findOwnInput(ownRef).getOrFail(OwnInputNotFound)
val out = getVaultOutput(tx, ownRef)
requireSameOwner(out, datum)
requireOutputToOwnAddress(
ownInput,
out,
NotExactlyOneVaultOutput
)
val requestTime = tx.validRange.getEarliestTime
val finalizationDeadline = requestTime + datum.waitTime
val newDatum = getVaultDatum(out)
require(newDatum.state.isPending, MustBePending)
require(
newDatum.finalizationDeadline == finalizationDeadline,
IncorrectDatumFinalization
)
}
private def finalize(tx: TxInfo, ownRef: TxOutRef, datum: Datum) = {
require(datum.state.isPending, ContractMustBePending)
require(tx.validRange.isAfter(datum.finalizationDeadline), DeadlineNotPassed)
val ownInput = tx.findOwnInput(ownRef).getOrFail(OwnInputNotFound)
requireEntireVaultIsSpent(datum, ownInput.resolved)
val scriptOutputs = tx.outputs.filter(out =>
out.address.credential === ownInput.resolved.address.credential
)
require(scriptOutputs.size == BigInt(0), WithdrawalsMustNotSendBackToVault)
val ownerOutputs =
tx.outputs.filter(out => addressEquals(out.address.credential, datum.owner))
require(ownerOutputs.size > BigInt(0), WrongAddressWithdrawal)
val totalToOwner =
ownerOutputs.foldLeft(BigInt(0))((acc, out) => acc + out.value.getLovelace)
require(totalToOwner == datum.amount, VaultAmountChanged)
}
private def cancel(tx: TxInfo, ownRef: TxOutRef, datum: Datum) = {
val out = getVaultOutput(tx, ownRef)
requireSameOwner(out, datum)
val vaultDatum = getVaultDatum(out)
require(vaultDatum.amount == datum.amount, VaultAmountChanged)
require(
out.value.getLovelace == datum.amount,
WrongOutputAmount
)
require(vaultDatum.state.isIdle, StateNotIdle)
require(vaultDatum.waitTime == datum.waitTime, WaitTimeChanged)
}
private def requireEntireVaultIsSpent(datum: Datum, output: TxOut) = {
val amountToSpend = datum.amount
val adaSpent = output.value.getLovelace
require(amountToSpend == adaSpent, AdaLeftover)
}
private def requireOutputToOwnAddress(ownInput: TxInInfo, out: TxOut, message: String) =
require(out.address.credential === ownInput.resolved.address.credential, message)
private def getVaultOutput(tx: TxInfo, ownRef: TxOutRef): TxOut = {
val ownInput = tx.findOwnInput(ownRef).getOrFail("Cannot find own input")
val scriptOutputs = tx.outputs.filter(out =>
out.address.credential === ownInput.resolved.address.credential
)
require(scriptOutputs.size == BigInt(1), NotExactlyOneVaultOutput)
scriptOutputs.head
}
private def getVaultDatum(vaultOutput: TxOut) = vaultOutput.datum match {
case ledger.api.v2.OutputDatum.OutputDatum(d) => d.to[Datum]
case _ => fail(NoDatumProvided)
}
private def requireSameOwner(out: TxOut, datum: Datum) =
out.datum match {
case scalus.ledger.api.v2.OutputDatum.OutputDatum(newDatum) =>
require(
newDatum.to[Datum].owner == datum.owner,
VaultOwnerChanged
)
case _ => fail(NoInlineDatum)
}
inline val NoDatumExists = "Contract has no datum"
inline val NoDatumProvided = "Vault transactions must have an inline datum"
inline val FinalizationDeadlineChanged =
"Deposit transactions must not change the finalization deadline"
inline val VaultAmountChanged = "Datum amount must match output lovelace amount"
inline val CannotAddTokens = "Deposits must only contain ADA"
inline val AdaNotConserved = "Deposits must add ADA to the vault"
inline val WrongDepositDestination =
"Deposit transactions can only be made to the vault"
inline val NotExactlyOneVaultOutput =
"Vault transaction must have exactly 1 output to the vault script"
inline val OwnInputNotFound = "Own input not found"
inline val IncorrectDatumFinalization =
"Finalization deadline must be request time plus wait time"
inline val MustBePending = "Output must have datum with State = Pending"
inline val WithdrawalAlreadyPending =
"Cannot withdraw, another withdrawal request is pending"
inline val WrongAddressWithdrawal =
"Withdrawal finalization must send funds to the vault owner"
inline val WithdrawalsMustNotSendBackToVault =
"Withdrawal finalization must not send funds back to the vault"
inline val DeadlineNotPassed =
"Finalization can only happen after the finalization deadline"
private inline val ContractMustBePending = "Contract must be Pending"
private inline val WrongOutputAmount = "Cancel transactions must not change the vault amount"
private inline val WaitTimeChanged = "Wait time must remain the same"
private inline val StateNotIdle = "Idle transactions must change the vault state to Idle"
private inline val NoInlineDatum = "Vault transactions must have an inline datum"
private inline val VaultOwnerChanged = "Vault transactions cannot change the vault owner"
private inline val AdaLeftover = "Must spend entire vault"
private def addressEquals(left: Credential, right: ByteString) = {
left match {
case v1.Credential.PubKeyCredential(hash) => hash.hash === right
case v1.Credential.ScriptCredential(hash) => hash === right
}
}
extension (s: State) {
def isPending: Boolean = s match {
case State.Idle => false
case State.Pending => true
}
def isIdle: Boolean = s match {
case State.Idle => true
case State.Pending => false
}
}
extension (i: Interval) {
def getEarliestTime: PosixTime = i.from.boundType match {
case IntervalBoundType.Finite(t) => t
case _ => BigInt(0)
}
def isAfter(timePoint: PosixTime): Boolean = i.from.boundType match {
case IntervalBoundType.Finite(time) => timePoint < time
case _ => false
}
}
}