Skip to content

Commit 22353f7

Browse files
authored
Merge pull request #20 from CoMakery/feat/176565860-v2
Detect transfer restriction function v2
2 parents ab80d5c + 7dd476d commit 22353f7

18 files changed

+391
-258
lines changed

README.md

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -126,14 +126,23 @@ There are a few interrelated account reference quirks to keep in mind:
126126
* `Txn.accounts[n]` for n > 0 will evaluate to the element at the n-1th index of the --app-account/ForeignAccounts transaction field.
127127
* Some versions of `tealdbg` show the Txn.Accounts array incorrectly. If n accounts are present in the transaction’s ForeignAccounts array, the debugger will show the sender’s account following by the first n-1 elements from ForeignAccounts.
128128

129+
## Teal contract size
130+
131+
The maximum size of the compiled teal contract is 1000 bytes.
132+
133+
On Mac OS you can check the file size by running:
134+
```
135+
python security_token.py && goal clerk compile security_token_approval.teal && stat -f%z security_token_approval.teal.tok
136+
```
137+
129138
# Use Cases
130139

131140
## Issuer Transfer Restrictions
132141
The Algorand Security Token can be configured after deployment to enforce transfer restrictions such as the ones shown in the diagram below. Each holders blockchain wallet address corresponds to a specific category. Only transfers between blockchain wallet address groups in the direction of the arrows are allowed:
133142

134143
![](diagrams/issuer_transfer_restriction_graph.png)
135144

136-
## Basic Issuance
145+
## Basic Transfer Restrictions Between Buyer and Seller Accounts
137146

138147
![](diagrams/basic_issuance.png)
139148

@@ -142,7 +151,7 @@ The Transfer Admin role and Wallet Admin role for the Token Contract can provisi
142151

143152
The Wallet Admin sets blockchain address permissions including the group, max token balance, address lock until date and if the address is frozen.
144153

145-
The transfer admin sets the rules for when transferring between wallet group types is allowed. Transfer rules define a rule from a group X to any target group Y. By default transfers between wallet groups are not allowed. The Transfer Admin must call setAllowGroupTransfer with a date after which transfers will be allowed for that pair.
154+
The transfer admin sets the rules for when transferring between wallet group types is allowed. Transfer rules define a rule from a group X to any target group Y. By default transfers between wallet groups are not allowed. The Transfer Admin must call setTransferRule with a date after which transfers will be allowed for that pair.
146155

147156
This is the process for configuring transfer restrictions and transferring tokens:
148157
1. A transfer rules admin configures which transfer groups can transfer to each other with "setAllowTransferGroups". Note that allowing a transfer from group A to group B by default does not allow the reverse transfer from group B to group A. This would have to be done separately. An example is that Reg CF unaccredited investors may be allowed to sell to Accredited US investors but not vice versa.
@@ -171,12 +180,13 @@ The TEAL assembly smart contract language uses program branches with no loops (i
171180
| CloseOut | called when closing out of the contract | forbidden |
172181
| [OptIn](bin/optin.sh) | Called by anyone who will use the app before they use the app | any account |
173182
| ["pause"](tests/pause_contract.test.js) | Freezes all transfers of the token for all token holders. | contract admin |
174-
| ["grantRoles"](tests/permissions.test.js) | Sets account contract permissions. Accepts 4-bit permissions integer. See: [Appendix 1: Roles Matrix](#roles-matrix) for details. | contract admin |
183+
| ["grantRoles"](tests/permissions.test.js) | Sets account contract permissions. Accepts an integer between 0 - 15 for the corresponding 4-bit bitmask permissions. See: [Appendix 1: Roles Matrix](#roles-matrix) for details. | contract admin |
175184
| ["setAddressPermissions"](tests/set_transfer_restrictions.test.js) | Sets account transfer restrictions:<br />1) `freeze` the address from making transfers. It can still receive transfers in.<br />2) `maxBalance` sets the max number of tokens an account can hold. Transfers into the address cannot exceed this amount.<br />3) `lockUntil` stops transfers from the address until the specified date. A locked address can still receive tokens but it cannot send them until the lockup time.<br />4) `transfer group` – sets the category of an address for use in transfer group rules. The default category is 1. | wallets admin |
176-
| ["setAllowGroupTransfer](bin/transfer-group-lock.sh) | Specifies a lockUntil time for transfers between a transfer from-group and a to-group. Transfers can between groups can only occur after the lockUntil time. The lockUntil time is specified as a Unix timestamp integer in seconds since the Unix Epoch. By default transfers beetween groups are not allowed. To allow a transfer set a timestamp in the past such as "1" - for the from and to group pair . The special transfer group default number "0" means the transfer is blocked. | transfer rules admin |
185+
| ["setTransferRule"](bin/transfer-group-lock.sh) | Specifies a lockUntil time for transfers between a transfer from-group and a to-group. Transfers can between groups can only occur after the lockUntil time. The lockUntil time is specified as a Unix timestamp integer in seconds since the Unix Epoch. By default transfers beetween groups are not allowed. To allow a transfer set a timestamp in the past such as "1" - for the from and to group pair . The special transfer group default number "0" means the transfer is blocked. | transfer rules admin |
177186
| ["mint"](bin/mint.sh) | Create new tokens from the reserve | reserve admin |
178187
| ["burn"](tests/burn.test.js) | Destroy tokens from a specified address | reserve admin |
179-
| ["transfer"](tests/transfer_restrictions.test.js) | Transfer from one account to another | any opted in account |
188+
| ["transfer"](tests/transfer_restrictions.test.js) | Transfer from one account to another if the transfer is allowed as determined by the transfer rules and the sender address permissions. | any opted in account |
189+
| ["detect"](tests/detect_transfer_restrictions.test.js) | Detects if a transfer can be made from one account to another. Contract execution fails if the transfer cannot be made based on the same restriction logic as transfer. This function can be used with `--dry-run` to determine if the transfer can be made or it can be part of a grouped transaction - e.g.for constructing atomic swaps for a dex that honors the transfer restrictions!!<br /><br />Why is it called "detect"? Because the Ethereum ERC-1404 restricted token standard specifies a function interface called "detectTransferRestriction". But TEAL restricts contract bytesize to 1000 bytes and each character referencing the "function" name uses a byte each time it's referenced. So we cut the function name down to "detect" to help keep the compiled TEAL contract under 1000 bytes. | any opted in account |
180190

181191
# Q&A
182192

@@ -205,23 +215,23 @@ Yes, to accommodate this we use functionality with the same function names and b
205215
TEAL smart contracts can’t directly call/invoke other contracts. But you can achieve something similar by having a contract only succeed if another stateful contract call is in the same transaction group as it. These articles describes how grouped transactions can be referenced by multiple contracts and processed atomically:
206216
* https://developer.algorand.org/articles/linking-algorand-stateful-and-stateless-smart-contracts
207217
* https://developer.algorand.org/docs/features/atomic_transfers/
208-
209-
This article describes how contracts can reference the state of another contract
218+
219+
The teal contracts can also read (but not write to) the state of other contracts. See the [PyTeal documentation](https://pyteal.readthedocs.io/en/stable/state.html#state-operation-table) for State Operations.
210220

211221
## How much stateful smart contract memory is allocated? Why?
212222

213223
The Algorand Stateful Smart Contract [documentation](https://developer.algorand.org/docs/features/asc1/stateful/#write-to-state) states:
214224

215225
> The number of global and local byte slices and integers also needs to be specified. These represent the absolute on-chain amount of space that the smart contract will use. Once set, these values can never be changed. Each key-value pair is allowed up to 64 bytes each (64-byte key and 64-byte value).
216226
217-
The examples of the contract evenly distribute memory allocation types, to allow flexibility for future upgrades of the smart contract by the administrator:
227+
The following is a reasonable distribution to allow flexibility for future upgrades of the smart contract by the administrator:
218228

219-
| TEAL Contract Memory Type | Allocation |
220-
| --- | --- |
221-
| local-ints | 8 |
222-
| local-byteslices | 8 |
223-
| global-ints | 32 |
224-
| global-byteslices | 32 |
229+
| TEAL Contract Memory Type | Allocation | Why |
230+
| --- | --- | --- |
231+
| local-byteslices | 8 of 16 total local | Even local distribution. |
232+
| local-ints | 8 of 16 total local | Even local distribution. |
233+
| global-byteslices | 54 of 64 total global | Each transfer rule takes a byteslice. Allocate most of the global memory to byteslices to leave room for many transfer rules between transfer groups. |
234+
| global-ints | 10 of 64 total global | There are at least 7 global ints. Leave room for a few more global ints in case of upgrades. |
225235

226236
If you know the memory requirements that you may need for future versions of the application you may want to vary these. Keep in mind that some of the memory locations may not be initialized at the time of deployment, but the memory will need to be available in order for the values to be stored in global or local memory.
227237

bin/allow-group-transfer.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ TO_GROUP_ID=$4
1010
LOCK_UNTIL_UNIX_TIMESTAMP=$5
1111

1212
# values passed to the app are:
13-
# app-arg0: "str:setAllowGroupTransfer"
13+
# app-arg0: "str:setTransferRule"
1414
# app-arg1: "int:from group id"
1515
# app-arg2: "int:to group id"
1616
# timestamp: unixtimestamp
1717

18-
goal app call --app-id $APP_ID --from $FROM --app-arg 'str:setAllowGroupTransfer' --app-arg "int:$FROM_GROUP_ID" --app-arg "int:$TO_GROUP_ID" --app-arg "int:$LOCK_UNTIL_UNIX_TIMESTAMP" -d devnet/Primary
18+
goal app call --app-id $APP_ID --from $FROM --app-arg 'str:setTransferRule' --app-arg "int:$FROM_GROUP_ID" --app-arg "int:$TO_GROUP_ID" --app-arg "int:$LOCK_UNTIL_UNIX_TIMESTAMP" -d devnet/Primary

bin/deploy-security-token.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ python security_token.py
1010
export ADDR_CREATOR=$1
1111
export TEAL_APPROVAL_PROG="security_token_approval.teal"
1212
export TEAL_CLEAR_PROG="security_token_clear_state.teal"
13-
export GLOBAL_BYTESLICES=32
14-
export GLOBAL_INTS=32
13+
export GLOBAL_BYTESLICES=54
14+
export GLOBAL_INTS=10
1515
export LOCAL_BYTESLICES=8
1616
export LOCAL_INTS=8
1717
export ALGORAND_DATA="devnet/Primary"

diagrams/basic_issuance.png

-1.69 KB
Loading

diagrams/diagrams.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ participant "Token Contract" as Token
66
actor "Transfer\nAdmin" as TAdmin
77
actor "Wallet\nAdmin" as WAdmin
88
9-
TAdmin -> Token: set "setAllowGroupTransfer" "lockUntil" time
9+
TAdmin -> Token: set "setTransferRule" "lockUntil" time
1010
Buyer -> WAdmin: send AML/KYC and accreditation info
11-
WAdmin -> Token: set buyer address permissions\n"lockUntil"\n"maxBalance"\n"transferGroup" // Reg D, S, or CF
11+
WAdmin -> Token: "setAddressPermissions" for buyer address\n"lockUntil"\n"maxBalance"\n"transferGroup" // Reg D, S, or CF
1212
activate Token
1313
Seller -> Token: transfer(buyerAddress, amount)
1414
Token -> Token: check transfer restrictions\n(from, to, time, maxBalance, lockup)

lib/algoUtil.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,8 @@ exports.deploySecurityToken = async (algodClient,
128128
let clearProgram = await exports.compileProgram(algodClient, clearTeal)
129129
let localInts = 8
130130
let localBytes = 8
131-
let globalInts = 32
132-
let globalBytes = 32
131+
let globalInts = 10
132+
let globalBytes = 54
133133

134134
let txn = algosdk.makeApplicationCreateTxn(sender, params, onComplete,
135135
approvalProgram, clearProgram,

0 commit comments

Comments
 (0)