Skip to content

Commit

Permalink
Merge pull request #21 from CoMakery/more-tests
Browse files Browse the repository at this point in the history
More tests
  • Loading branch information
aquabu authored Feb 3, 2021
2 parents 22353f7 + 03b2db9 commit fce81c6
Show file tree
Hide file tree
Showing 5 changed files with 183 additions and 24 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: Tests

on: [push, pull_request]
on: [push]

jobs:
test:
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,9 @@ There are lots of other reasons you may get a bad request error, such as TEAL ex
There are a few interrelated account reference quirks to keep in mind:
* `Txn.accounts[0]` will always evaluate to `Txn.sender()`
* `Txn.accounts[1]` is the first `--app-account` item.
* If no `--app-account` items are included, Txn.accounts.length() will be 0 but `Txn.accounts[0]` still resolves to the sender.
* `Txn.accounts[n]` for n > 0 will evaluate to the element at the n-1th index of the --app-account/ForeignAccounts transaction field.
* 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.
* If no `--app-account` items are included, `Txn.accounts.length()` will be 0 but `Txn.accounts[0]` still resolves to the sender.
* `Txn.accounts[n]` for n > 0 will evaluate to the element at the n-1th index of the `--app-account` or `ForeignAccounts` transaction field. For example `Txn.accounts[2]` would refer to `appAccount[1]`. Another way to put it is the `--from` address is shifted into Txn.accounts[0] and `--app-accounts` are shifted right by 1 position.
* 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`.

## Teal contract size

Expand Down Expand Up @@ -208,7 +208,7 @@ It is recommended that all admin actions should be performed by accounts other t

## QSP-8 Do Algorand Smart Contracts Lack A Standard Like the Ethereum ERC20 Token?

Yes, to accommodate this we use functionality with the same function names and behavior as the Ethereum ERC20 token standard - with the notable exception that the contract does not implement the `approve()` and `transferFrom` functions. See next question...
Yes, to accommodate this we use functionality with the same function names and behavior as the Ethereum ERC20 token standard - with the notable exception that the contract does not implement the `approve()` and `transferFrom()` functions. See next question...

## QSP-9 Why doesn't the contract implement the approve() and transferFrom() functions from the ERC20 standard?

Expand Down
16 changes: 11 additions & 5 deletions tests/grant_roles.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const { describe } = require('yargs')
const server = "http://127.0.0.1"
const port = 8080

var adminAccount, receiverAccount, token, clientV2, appId
var adminAccount, receiverAccount, token, clientV2, appId, localState

beforeEach(async () => {
await privateTestNetSetup(appId)
Expand All @@ -23,17 +23,23 @@ beforeEach(async () => {
await util.optInApp(clientV2, receiverAccount, appId)
})

test('contract admin role can be granted by contract admin', async () => {
async function grantRoles(roleId, from=adminAccount, target=receiverAccount) {
appArgs = [
EncodeBytes("grantRoles"),
EncodeUint('8')
EncodeUint(roleId)
]
await util.appCall(clientV2, adminAccount, appId, appArgs, [receiverAccount.addr])
await util.appCall(clientV2, from, appId, appArgs, [target.addr])
}

test('contract admin role can be granted by contract admin', async () => {
await grantRoles(8, adminAccount, receiverAccount)

localState = await util.readLocalState(clientV2, receiverAccount, appId)
expect(localState["roles"]["ui"]).toEqual(8)

await util.appCall(clientV2, receiverAccount, appId, appArgs, [receiverAccount.addr])
await grantRoles(15, receiverAccount, receiverAccount)
localState = await util.readLocalState(clientV2, receiverAccount, appId)
expect(localState["roles"]["ui"]).toEqual(15)
})

test('contract admin role can be revoked by contract admin', async () => {
Expand Down
22 changes: 22 additions & 0 deletions tests/max_token_balance.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,28 @@ test('blocks transfers that exceed the addresses maxBalance but not lesser amoun
appArgs = [EncodeBytes("transfer"), EncodeUint('10')]
await util.appCall(clientV2, receiverAccount, appId, appArgs, [adminAccount.addr])

// tokens sent back to admin
localState = await util.readLocalState(clientV2, receiverAccount, appId)
expect(localState["balance"]["ui"]).toEqual(undefined)
})

test('maxBalance of 0 is treated as no max balance', async () => {
let maxTokenBalance = 0
appArgs = [EncodeBytes("setAddressPermissions"), EncodeUint('0'), EncodeUint(`${maxTokenBalance}`), EncodeUint('0'), EncodeUint('1')]
await util.appCall(clientV2, adminAccount, appId, appArgs, [receiverAccount.addr])

// allow token transfers to address with 0 maxBalance
appArgs = [EncodeBytes("transfer"), EncodeUint('10')]
await util.appCall(clientV2, adminAccount, appId, appArgs, [receiverAccount.addr])

// tokens sent to receiver
localState = await util.readLocalState(clientV2, receiverAccount, appId)
expect(localState["balance"]["ui"]).toEqual(10)

// allow tokens to be transferred out of the account
appArgs = [EncodeBytes("transfer"), EncodeUint('10')]
await util.appCall(clientV2, receiverAccount, appId, appArgs, [adminAccount.addr])

// tokens sent back to admin
localState = await util.readLocalState(clientV2, receiverAccount, appId)
expect(localState["balance"]["ui"]).toEqual(undefined)
Expand Down
159 changes: 145 additions & 14 deletions tests/transfer_restrictions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,25 @@ test('has expected starting test state', async () => {
expect(localState["transfer admin"]).toEqual(undefined)
})

test('simple transfer', async () => {
test('cannot transfer by default from and to the default group 1 -> 1', async () => {
try {
appArgs = [EncodeBytes("transfer"), EncodeUint('11')]
await util.appCall(clientV2, adminAccount, appId, appArgs, [receiverAccount.addr])
} catch (e) {
expect(e.message).toEqual("Bad Request")
}
// check first receiver got no tokens and is in group 1
let localState = await util.readLocalState(clientV2, receiverAccount, appId)
expect(localState["transferGroup"]["ui"]).toEqual(1)
expect(localState["balance"]["ui"]).toEqual(undefined)

// check sender sent no tokens and is in group 1
localState = await util.readLocalState(clientV2, adminAccount, appId)
expect(localState["transferGroup"]["ui"]).toEqual(1)
expect(localState["balance"]["ui"]).toEqual(27)
})

test('simple transfer back and forth: with group 1 -> 1 permitted', async () => {
let fromGroupId = 1
let toGroupId = 1
let earliestPermittedTime = 1
Expand Down Expand Up @@ -80,6 +98,24 @@ test('simple transfer', async () => {
globalState = await util.readGlobalState(clientV2, adminAccount, appId)
expect(globalState['cap']['ui'].toString()).toEqual('80000000000000000')
expect(globalState['reserve']['ui'].toString()).toEqual('79999999999999973')

// ======
//transfer back
appArgs = [EncodeBytes("transfer"), EncodeUint('11')]
await util.appCall(clientV2, receiverAccount, appId, appArgs, [adminAccount.addr])

// check original sender got tokens back
localState = await util.readLocalState(clientV2, adminAccount, appId)
expect(localState["balance"]["ui"]).toEqual(27)

// check tokens deducted
localState = await util.readLocalState(clientV2, receiverAccount, appId)
expect(localState["balance"]["ui"]).toEqual(undefined)

// check global supply is same
globalState = await util.readGlobalState(clientV2, adminAccount, appId)
expect(globalState['cap']['ui'].toString()).toEqual('80000000000000000')
expect(globalState['reserve']['ui'].toString()).toEqual('79999999999999973')
})

test('can lock the default address category for transfers', async () => {
Expand Down Expand Up @@ -107,6 +143,64 @@ test('can lock the default address category for transfers', async () => {
expect(localState["balance"]["ui"]).toEqual(undefined)
})

test('simple transfer from group 0 -> 0 works when permitted', async () => {
let fromGroupId = 0
let toGroupId = 0
let earliestPermittedTime = 1

let transferGroupLock =
`goal app call --app-id ${appId} --from ${adminAccount.addr} ` +
`--app-arg 'str:setTransferRule' ` +
`--app-arg "int:${fromGroupId}" --app-arg "int:${toGroupId}" ` +
`--app-arg "int:${earliestPermittedTime}" -d devnet/Primary`

appArgs = [EncodeBytes("setAddressPermissions"), EncodeUint('0'), EncodeUint('0'), EncodeUint('0'), EncodeUint('0')]
await util.appCall(clientV2, adminAccount, appId, appArgs, [adminAccount.addr])
await util.appCall(clientV2, adminAccount, appId, appArgs, [receiverAccount.addr])

await shell.exec(transferGroupLock, {async: false, silent: false})

globalState = await util.readGlobalState(clientV2, adminAccount, appId)
expect(globalState['reserve']['ui'].toString()).toEqual('79999999999999973')

//transfer
appArgs = [EncodeBytes("transfer"), EncodeUint('11')]
await util.appCall(clientV2, adminAccount, appId, appArgs, [receiverAccount.addr])

// check receiver got tokens
localState = await util.readLocalState(clientV2, receiverAccount, appId)
expect(localState["balance"]["ui"]).toEqual(11)
expect(localState["transferGroup"]["ui"]).toEqual(undefined)

// check sender has less tokens
localState = await util.readLocalState(clientV2, adminAccount, appId)
expect(localState["balance"]["ui"]).toEqual(16)
expect(localState["transferGroup"]["ui"]).toEqual(undefined)

// check global supply is same
globalState = await util.readGlobalState(clientV2, adminAccount, appId)
expect(globalState['cap']['ui'].toString()).toEqual('80000000000000000')
expect(globalState['reserve']['ui'].toString()).toEqual('79999999999999973')

// ======
//transfer back
appArgs = [EncodeBytes("transfer"), EncodeUint('11')]
await util.appCall(clientV2, receiverAccount, appId, appArgs, [adminAccount.addr])

// check original sender got tokens back
localState = await util.readLocalState(clientV2, adminAccount, appId)
expect(localState["balance"]["ui"]).toEqual(27)

// check tokens deducted
localState = await util.readLocalState(clientV2, receiverAccount, appId)
expect(localState["balance"]["ui"]).toEqual(undefined)

// check global supply is same
globalState = await util.readGlobalState(clientV2, adminAccount, appId)
expect(globalState['cap']['ui'].toString()).toEqual('80000000000000000')
expect(globalState['reserve']['ui'].toString()).toEqual('79999999999999973')
})

test('can transfer to an account if the transfer rule lock has expired', async () => {
let fromGroupId = 1
let toGroupId = 1
Expand All @@ -128,20 +222,7 @@ test('can transfer to an account if the transfer rule lock has expired', async (
expect(localState["balance"]["ui"]).toEqual(11)
})

test('cannot transfer by default', async () => {
try {
appArgs = [EncodeBytes("transfer"), EncodeUint('11')]
await util.appCall(clientV2, adminAccount, appId, appArgs, [receiverAccount.addr])
} catch (e) {
expect(e.message).toEqual("Bad Request")
}
// check first receiver got tokens
localState = await util.readLocalState(clientV2, receiverAccount, appId)
expect(localState["balance"]["ui"]).toEqual(undefined)
})

test('can transfer between permitted account groups', async () => {

let earliestPermittedTime = 1
// from group 1 -> 1 is allowed
let transferGroupLock1 =
Expand Down Expand Up @@ -191,4 +272,54 @@ test('can transfer between permitted account groups', async () => {
// first account no longer has the transferred tokens
localState = await util.readLocalState(clientV2, receiverAccount, appId)
expect(localState["balance"]["ui"]).toEqual(4)
})

test('transferRule allowing transfer from group 1 to 2 does not allow transfers from 2 to 1 (the reverse rule)', async () => {
let earliestPermittedTime = 1

// from group 1 -> 2 is allowed
let transferGroupLock2 =
`goal app call --app-id ${appId} --from ${adminAccount.addr} ` +
`--app-arg 'str:setTransferRule' ` +
`--app-arg "int:1" --app-arg "int:2" ` +
`--app-arg "int:${earliestPermittedTime}" -d devnet/Primary`

await shell.exec(transferGroupLock2, {async: false, silent: false})

// put receiver in group 2
appArgs = [EncodeBytes("setAddressPermissions"), EncodeUint('0'), EncodeUint('0'), EncodeUint('0'), EncodeUint('2')]
await util.appCall(clientV2, adminAccount, appId, appArgs, [receiverAccount.addr])

let localState = await util.readLocalState(clientV2, receiverAccount, appId)
expect(localState["balance"]["ui"]).toEqual(undefined)
expect(localState["transferGroup"]["ui"].toString()).toEqual('2')

//transfer to receiver (group 1 -> 2)
appArgs = [EncodeBytes("transfer"), EncodeUint('7')]
await util.appCall(clientV2, adminAccount, appId, appArgs, [receiverAccount.addr])

// check receiver got tokens
localState = await util.readLocalState(clientV2, receiverAccount, appId)
expect(localState["balance"]["ui"]).toEqual(7)

// first sender adminAccount no longer has the transferred tokens
localState = await util.readLocalState(clientV2, adminAccount, appId)
expect(localState["balance"]["ui"]).toEqual(20)

// transfer back from receiver account (group 2 -> 1) FAILS!
let error = null
try {
appArgs = [EncodeBytes("transfer"), EncodeUint('7')]
await util.appCall(clientV2, receiverAccount, appId, appArgs, [adminAccount.addr])
} catch(e) {
error = e
}
expect(error.message).toBe("Bad Request")

//balances remain unchanged before and after the failed transfer
localState = await util.readLocalState(clientV2, receiverAccount, appId)
expect(localState["balance"]["ui"]).toEqual(7)

localState = await util.readLocalState(clientV2, adminAccount, appId)
expect(localState["balance"]["ui"]).toEqual(20)
})

0 comments on commit fce81c6

Please sign in to comment.