This contract is an extension of the A2 standard. It allows to manage permissions regarding asset transfers on-chain.
This whitelist contract is coded in Archetype. To be able to run the tests, you need to install the Completium CLI:
> npm ci
> npm run completium_init
> npm run completium_add_users
> npm run mockup_init
> npm run test
To use the mockup mode, you need to have the octez-client
installed. You can find instructions on how to do that here: https://assets.tqtezos.com/docs/setup/1-tezos-client/
start
if (from in restricted list?) then (yes)
#pink:error: FROM_RESTRICTED;
kill
elseif (to in restricted list?) then (yes)
#pink:error: TO_RESTRICTED;
kill
elseif (is sender a super user ?) then (yes)
#palegreen:transfer is valid;
stop
elseif (is TO transfer list in FROM allowed lists ?) then (yes)
#palegreen:transfer is valid;
stop
else (no)
#pink:error: TO_NOT_ALLOWED;
kill
@enduml
Consider the following examples extracted from the A2 standard.
"alice": 0
"bob": 0
"charlie": 1
"dan": 2
0: (unrestricted: True, allowedTransferlists: {0, 2})
1: (unrestricted: True, allowedTransferlists: {1})
2: (unrestricted: False, allowedTransferlists: {1, 2})
Then suppose the following call to assertTransfers were made:
assertTransfers
{ Pair "alice" { "bob", "dan" }
, Pair "bob" { "alice" }
, Pair "charlie" { "charlie", "dan" }
}
alice -> bob
: alice and bob are on the same transferlist (0
), which contains itself in itsallowedTransferlists
and isunrestricted
, so this succeedsalice -> dan
: alice is on a transferlist (0
) that containsdan's
transferlistId (2
) in itsallowedTransferlists
and isunrestricted
, but it fails becausedan
's transferlist is restrictedbob -> alice
: This succeeds by the same logic asalice -> bob
: they're on the sameunrestricted
transferlist that contains its own transferlistId in itsallowedTransferlists
charlie -> charlie
: This succeeds sincecharlie
's transferlist is unrestricted and contains its own transferlistId in itsallowedTransferlists
charlie -> dan
: This fails becausedan
's transferlist (2
) is restricted
Thus the above call to assertTransfers
will fail.
This implementation introduces a new feature: the super user
. The super user
has the ability to trigger a transfer between two addresses in whitelists
not allowed to transfer between each other. When the transfer is triggered by a super user, we only verify that the user list is not restriced.
We do not verify the allowed transfer lists.
It will only work if the address is not in a restricted whitelist
If we take back the data we used in the above example, and the following transfers:
assertTransfers triggered by super user
{ Pair "charlie" { "charlie" }
, Pair "alice" { "charlie" }
, Pair "bob" { "dan" }
, Pair "dan" { "charlie" }
}
charlie -> charlie
:charlie
is in transferlist (1
), which isunrestricted
, so this succeeds since it is triggered by the super user.alice -> charlie
:alice
is on a transferlist (0
) andcharlie
is in transferlist (1
), bothunrestricted
, so this succeeds.bob -> dan
:bob
is on a transferlist (0
) anddan
is in transferlist (2
) which isrestricted
, so this failsdan -> charlie
:bob
is on a transferlist (0
) anddan
is in transferlist (2
) which isrestricted
, so this fails
map users {
[USER] => [TRANSFER LIST]
alice => 1
bob => 2
}
object super_users {
charly
}
map transfer_lists {
[ID] => [UNRESTRICTED, ALLOWED_LISTS]
1 => True, [0,1]
2 => True, [1,2]
3 => False, [2]
}
Method | Restrictions | Parameters | Michelson example |
---|---|---|---|
setAdmin | Only callable by contract owner | address | "tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb" |
addSuperuser | Only callable by contract owner | address | "tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb" |
updateUser | Only callable by contract owner | (pair (address user) (nat listId)) | (Pair "tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb" 1) |
updateUsers | Only callable by contract owner | (list (pair address (option nat))) | { (Pair "tz1f2GJ6NGePFcKdjNccDWmLJxb1rkJ6uZLe" (Some 0)) ; (Pair "tz1depofsXNUscfZwRx57nsgziafHuGbC8eo" (Some 1)) } |
updateTransferlist | Only callable by contract owner | (pair (nat %transferlistId) (option %u (pair bool (set nat)))) | (Pair (0) (Some (Pair True (set 0)))) |
removeSuperuser | Only callable by contract owner | address | "tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb" |
assertReceivers | None | (list address) | { "tz1depofsXNUscfZwRx57nsgziafHuGbC8eo" ; "tz1TbYXorbD6kX4qW27QJcRFpSxfJA4JWQkR" } |
assertTransfers | None | (list (pair address (list address))) | { (Pair "tz1TbYXorbD6kX4qW27QJcRFpSxfJA4JWQkR" { "tz1depofsXNUscfZwRx57nsgziafHuGbC8eo" }) } |
assertTransferlist | None | (pair (nat %fromTransferListId) (nat %toTransferListId)) | (Pair 0 1) |
Element | Type |
---|---|
admin | address |
users | big_map(address, nat) |
transferlists | big_map(nat, $transferlists_value) |
superusers | set(address) |
Element | Type |
---|---|
transferlists_value | pair(unrestricted bool, allowedTransferlists set(nat)) |
Entrypoint | Test case | Expected result | Progress |
---|---|---|---|
Set admin | |||
Set admin as non admin should fail | Error: INVALID_CALLER | ✅ Done | |
Set admin should succeed | Success | ✅ Done | |
Add super user | |||
Add super user in whitelist contract as non admin should fail | Error: INVALID_CALLER | ✅ Done | |
Add super user in whitelist contract as admin should succeed | Success | ✅ Done | |
Add an already existing super user in whitelist contract as admin should succeed | Success | ✅ Done | |
Update user | |||
Update a non existing user in whitelist contract as non admin should fail | Error: INVALID_CALLER | ✅ Done | |
Update a non existing user in whitelist contract as admin should succeed | Success | ✅ Done | |
Update a non existing user in whitelist contract with no whitelist id as admin should succeed | Success | ✅ Done | |
Update an existing user in whitelist contract with whitelist id as admin should succeed | Success | ✅ Done | |
Update users | |||
Update non existing users in whitelist contract as non admin should fail | Error: INVALID_CALLER | ✅ Done | |
Update non existing users in whitelist contract as admin should succeed | Success | ✅ Done | |
Update existing users in whitelist contract as admin should succeed | Success | ✅ Done | |
Update transfer list | |||
Update non existing transfer list as non admin should fail | Error: INVALID_CALLER | ✅ Done | |
Update non existing transfer list as admin should succeed | Success | ✅ Done | |
Update non existing transfer list as admin with no allowed lists should succeed | Success | ✅ Done | |
Update existing transfer list as admin with no allowed lists should succeed | Success | ✅ Done | |
Update existing transfer list as admin should succeed | Success | ✅ Done | |
Update existing transfer list with null to delete it as admin should succeed | Success | ✅ Done | |
Remove super user | |||
Remove super user in whitelist contract as non admin should fail | Error: INVALID_CALLER | ✅ Done | |
Remove non existing super user from whitelist contract should succeed | Success | ✅ Done | |
Remove existing super user from whitelist contract should succeed | Success | ✅ Done | |
Assert receivers | |||
Assert receivers with only restricted users should fail | Error: USER_RESTRICTED | ✅ Done | |
Assert receivers with restricted and non restricted users should fail | Error: USER_RESTRICTED | ✅ Done | |
Assert receivers with unknown users should fail | Error: USER_RESTRICTED | ✅ Done | |
Assert receivers with users without allowed list should fail | Error: USER_RESTRICTED | ✅ Done | |
Assert receivers with unrestricted users should succeed | Success | ✅ Done | |
Assert transfers | |||
Assert transfers [FROM: restriced, TO: restriced] should fail | Error: FROM_RESTRICTED | ✅ Done | |
Assert transfers [FROM: not whitelisted, TO: not whitelisted] should fail | Error: FROM_RESTRICTED | ✅ Done | |
Assert transfers [FROM: redtricted, TO: not whitelisted] should fail | Error: FROM_RESTRICTED | ✅ Done | |
Assert transfers [FROM: not whitelisted, TO: redtricted] should fail | Error: FROM_RESTRICTED | ✅ Done | |
Assert transfers [FROM: whitelisted unrestricted, TO: restricted] should fail | Error: TO_RESTRICTED | ✅ Done | |
Assert transfers [FROM: whitelisted unrestricted, TO: not whitelisted] should fail | Error: TO_RESTRICTED | ✅ Done | |
Assert transfers [FROM: whitelisted unrestricted, TO: not in FROM allowed list] should fail | Error: TO_RESTRICTED | ✅ Done | |
Assert transfers [FROM: whitelisted unrestricted, TO: in FROM allowed list] should succeed | Success | ✅ Done | |
Assert transfers [FROM: not whitelisted, TO: not whitelisted, SENDER: SUPERUSER] should fail | Error: FROM_RESTRICTED | ✅ Done | |
Assert transfers [FROM: whitelisted, TO: not whitelisted, SENDER: SUPERUSER] should fail | Error: TO_RESTRICTED | ✅ Done | |
Assert transfers [FROM: restricted, TO: not whitelisted, SENDER: SUPERUSER] should fail | Error: FROM_RESTRICTED | ✅ Done | |
Assert transfers [FROM: not whitelisted, TO: restricted, SENDER: SUPERUSER] should fail | Error: FROM_RESTRICTED | ✅ Done | |
Assert transfers [FROM: restricted, TO: not in FROM allowed list, SENDER: SUPERUSER] should fail | Error: FROM_RESTRICTED | ✅ Done | |
Assert transfers [FROM: unrestricted, TO: restricted, SENDER: SUPERUSER] should fail | Error: TO_RESTRICTED | ✅ Done | |
Assert transfers [FROM: whitelisted unrestricted, TO: in FROM allowed list, , SENDER: SUPERUSER] should succeed | Success | ✅ Done | |
Assert transfer list | |||
Assert transfer list with non existing from transfer list should fail | Error: FROM_TRANSFERLIST_NOT_FOUND | ✅ Done | |
Assert transfer list with non existing to transfer list should fail | Error: TO_TRANSFERLIST_NOT_FOUND | ✅ Done | |
Assert transfer list with restricted existing from transfer list should fail | Error: FROM_INVALID_UNRESTRICTED_STATE | ✅ Done | |
Assert transfer list with restricted existing to transfer list should fail | Error: TO_INVALID_UNRESTRICTED_STATE | ✅ Done | |
Assert transfer list with to transfer list not in from allowed lists should fail | Error: TO_TRANSFERLIST_NOT_FOUND_IN_FROM | ✅ Done | |
Assert transfer list with to transfer list in from allowed lists should succeed | Success | ✅ Done |