-
-
Notifications
You must be signed in to change notification settings - Fork 30
/
Copy pathwrapped_token.py
100 lines (86 loc) · 3.75 KB
/
wrapped_token.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#!opshin
from opshin.prelude import *
@dataclass
class Empty(PlutusData):
CONSTR_ID = 0
pass
def all_tokens_unlocked_from_contract_address(
txins: List[TxInInfo], address: Address, token: Token
) -> int:
# generally always iterate over all inputs to avoid double spending
res = 0
for txi in txins:
if txi.resolved.address == address:
res += txi.resolved.value.get(token.policy_id, {b"": 0}).get(
token.token_name, 0
)
return res
def own_spent_utxo(txins: List[TxInInfo], p: Spending) -> TxOut:
# obtain the resolved txout that is going to be spent from this contract address
for txi in txins:
if txi.out_ref == p.tx_out_ref:
own_txout = txi.resolved
# This throws a name error if the txout was not found
return own_txout
def own_policy_id(own_spent_utxo: TxOut) -> PolicyId:
# obtain the policy id for which this contract can validate minting/burning
cred = own_spent_utxo.address.payment_credential
if isinstance(cred, ScriptCredential):
policy_id = cred.credential_hash
# This throws a name error if the credential is not a ScriptCredential instance
return policy_id
def own_address(own_policy_id: PolicyId) -> Address:
return Address(ScriptCredential(own_policy_id), NoStakingCredential())
def all_tokens_locked_at_contract_address(
txouts: List[TxOut], address: Address, token: Token
) -> int:
res = 0
for txo in txouts:
if txo.address == address:
res += txo.value.get(token.policy_id, {b"": 0}).get(token.token_name, 0)
# enforce a small inlined datum
assert txo.datum == SomeOutputDatum(
b""
), f"Does not attach correct datum to script output, got {txo.datum} but expected {b''}"
return res
# this is a parameterized contract. The first three arguments are
# parameters controlling which token is to be wrapped and how many decimal places to add
# compile the contract as follows to obtain the parameterized contract (for preprod milk)
#
# moreover this contract should always be called with three virtual parameters, so enable --force-three-params
#
# $ opshin build spending examples/smart_contracts/wrapped_token.py '{"bytes": "ae810731b5d21c0d182d89c60a1eff7095dffd1c0dce8707a8611099"}' '{"bytes": "4d494c4b"}' '{"int": 1000000}' --force-three-params
def validator(
token_policy_id: bytes,
token_name: bytes,
wrapping_factor: int,
_datum: Nothing,
_redeemer: NoRedeemer,
ctx: ScriptContext,
) -> None:
purpose = ctx.purpose
if isinstance(purpose, Minting):
# whenever tokens should be burned/minted, the minting purpose will be triggered
own_addr = own_address(purpose.policy_id)
own_pid = purpose.policy_id
elif isinstance(purpose, Spending):
# whenever something is unlocked from the contract, the spending purpose will be triggered
own_utxo = own_spent_utxo(ctx.tx_info.inputs, purpose)
own_pid = own_policy_id(own_utxo)
own_addr = own_utxo.address
else:
assert False, "Incorrect purpose given"
token = Token(token_policy_id, token_name)
all_locked = all_tokens_locked_at_contract_address(
ctx.tx_info.outputs, own_addr, token
)
all_unlocked = all_tokens_unlocked_from_contract_address(
ctx.tx_info.inputs, own_addr, token
)
all_minted = ctx.tx_info.mint.get(own_pid, {b"": 0}).get(b"w" + token_name, 0)
print(f"unlocked from contract: {all_unlocked}")
print(f"locked at contract: {all_locked}")
print(f"minted: {all_minted}")
assert (
(all_locked - all_unlocked) * wrapping_factor
) == all_minted, f"Wrong amount of tokens minted, difference: {(all_locked - all_unlocked) * wrapping_factor - all_minted}"