Skip to content

Commit

Permalink
[WIP] refactor!: make escrowAccess values strings
Browse files Browse the repository at this point in the history
Rather than store an array of accounts with escrow access, store only
one account. While a less practical implementation, it does conform to
the spec and simplifies the code.

In addition, remove non-sensical token_id existence requirements for
`grant_access` and `remove_access` given by current version of
near/NEPs#4

BREAKING CHANGE: this intentionally deviates from the current spec
Co-Authored-By: @amgando
  • Loading branch information
TenzingSh committed May 20, 2020
1 parent b05c6e7 commit bd39400
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 77 deletions.
37 changes: 13 additions & 24 deletions contracts/assemblyscript/nep4/__tests__/main.unit.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,42 +17,30 @@ const carol = 'carol'
describe('grant_access', () => {
it('grants access to the given account_id for all the tokens that account has', () => {
const aliceToken = nonSpec.mint_to(alice)
Context.setPredecessor_account_id(alice)

Context.setPredecessor_account_id(alice)
grant_access(bob)

expect(check_access(bob)).toBe(true)
})

it('requires the caller of the function to have access to the token.', () => {
expect(() => {
const aliceToken = nonSpec.mint_to(alice)
Context.setPredecessor_account_id(bob)

grant_access(bob);
}).toThrow(nonSpec.ERROR_NO_TOKENS_CONTROLLED)
Context.setPredecessor_account_id(bob)
expect(check_access(alice)).toBe(true)
})
});

describe('revoke_access', () => {
it('revokes access to the given `accountId` for the given `tokenId`', () => {
const aliceToken = nonSpec.mint_to(alice)
Context.setPredecessor_account_id(alice)

Context.setPredecessor_account_id(alice)
grant_access(bob);
expect(check_access(bob)).toBe(true)

revoke_access(bob);
expect(check_access(bob)).toBe(false)
})
Context.setPredecessor_account_id(bob)
expect(check_access(alice)).toBe(true)

it('requires caller of the function to have access to the token.', () => {
expect(() => {
const aliceToken = nonSpec.mint_to(alice)
Context.setPredecessor_account_id(bob)
Context.setPredecessor_account_id(alice)
revoke_access(bob);

revoke_access(bob);
}).toThrow(nonSpec.ERROR_NO_TOKENS_CONTROLLED)
Context.setPredecessor_account_id(bob)
expect(check_access(alice)).toBe(false)
})
})

Expand Down Expand Up @@ -118,11 +106,12 @@ describe('transfer', () => {
describe('check_access', () => {
it('returns true if caller of the function has access to the token', () => {
const aliceToken = nonSpec.mint_to(alice)
Context.setPredecessor_account_id(alice)

Context.setPredecessor_account_id(alice)
grant_access(bob)

expect(check_access(bob)).toBe(true)
Context.setPredecessor_account_id(bob)
expect(check_access(alice)).toBe(true)
})

it('returns false if caller of function does not have access', () => {
Expand Down
69 changes: 16 additions & 53 deletions contracts/assemblyscript/nep4/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ type TokenId = u64
export const MAX_SUPPLY = u64(1_000)

const tokenToOwner = new PersistentMap<TokenId, AccountId>('a')
const escrowAccess = new PersistentMap<AccountId, AccountId[]>('b')
const escrowAccess = new PersistentMap<AccountId, AccountId>('b')
const TOTAL_SUPPLY = 'c'

/******************/
/* ERROR MESSAGES */
/******************/

export const ERROR_NO_TOKENS_CONTROLLED = "Caller does not control any tokens."
export const ERROR_NO_ESCROW_REGISTERED = "Caller has no escrow registered."
export const ERROR_CALLER_ID_DOES_NOT_MATCH_EXPECTATION = "Caller ID does not match expectation."
export const ERROR_CLAIMED_OWNER_DOES_NOT_OWN_TOKEN = "Claimed owner does not own token"
export const ERROR_MAXIMUM_TOKEN_LIMIT_REACHED = "Maximum token limit reached."
Expand All @@ -27,47 +27,13 @@ export const ERROR_MAXIMUM_TOKEN_LIMIT_REACHED = "Maximum token limit reached."
/******************/

// Grant the access to the given `accountId` for all tokens that account has.
// Requirements:
// * The caller of the function (`predecessor`) should have access to the tokens.
export function grant_access(escrow_account_id: string): void {
const predecessor = context.predecessor

// fetch all accounts with escrow access to predecessor tokens
assert(escrowAccess.contains(predecessor), ERROR_NO_TOKENS_CONTROLLED)
const escrowAccounts = escrowAccess.getSome(predecessor)

// add escrow_account_id to list of escrow accounts
escrowAccounts.push(escrow_account_id)

// grant escrow account access to all predecessor tokens
escrowAccess.set(predecessor, escrowAccounts)
escrowAccess.set(context.predecessor, escrow_account_id)
}

// Revoke the access to the given `accountId` for the given `tokenId`.
// Requirements:
// * The caller of the function (`predecessor`) should have access to the token.
// Revoke the access to the given `accountId` for all tokens that account has.
export function revoke_access(escrow_account_id: string): void {
const predecessor = context.predecessor

// fetch escrow_account_id from list of escrow accounts
assert(escrowAccess.contains(predecessor), ERROR_NO_TOKENS_CONTROLLED)
const escrowAccounts = escrowAccess.getSome(predecessor)

// remove the escrow account from list of accounts with access to owner's tokens
// note we can't use Array<string>.filter() since it's not implemented in AssemblyScript
// and only number types seem to work with Array<T>.filter()
// also there are no clojures in AssemblyScript yet so passing in the value to filter on
// seems like a non-starter. for loops FTW!
let filteredAccounts: AccountId[] = []
for (let index = 0; index < escrowAccounts.length; index++) {
const account = escrowAccounts[index]
if(account != escrow_account_id) {
filteredAccounts.push(account)
}
}

// commit the removal to the escrow registry
escrowAccess.set(predecessor, filteredAccounts)
escrowAccess.delete(context.predecessor)
}

// Transfer the given `token_id` from the given `owner_id`. Account `new_owner_id` becomes the new owner.
Expand All @@ -77,8 +43,8 @@ export function transfer_from(owner_id: string, new_owner_id: string, token_id:
const predecessor = context.predecessor

// fetch token escrow & verify access
const escrowAccounts = escrowAccess.getSome(owner_id)
assert(escrowAccounts.includes(predecessor), ERROR_CALLER_ID_DOES_NOT_MATCH_EXPECTATION)
const escrow = escrowAccess.getSome(owner_id)
assert(escrow == predecessor, ERROR_CALLER_ID_DOES_NOT_MATCH_EXPECTATION)

// assign new owner to token
tokenToOwner.set(token_id, new_owner_id)
Expand All @@ -102,20 +68,18 @@ export function transfer(new_owner_id: string, token_id: TokenId): void {
/* VIEW METHODS */
/****************/

// Returns `true` or `false` based on caller of the function (`predecessor`) having access to a user's tokens
export function check_access(account_id: string): bool {
const owner = context.predecessor
// Returns `true` or `false` based on caller of the function (`predecessor`) having access to account_id's tokens
export function check_access(account_id: string): boolean {
const caller = context.predecessor

assert(caller != account_id, ERROR_CALLER_ID_DOES_NOT_MATCH_EXPECTATION)

// confirm the caller has been minted at least one token
if(!escrowAccess.contains(owner)) {
// caller has never been minted a token and does not appear in the registry
if (!escrowAccess.contains(account_id)) {
return false
} else {
// fetch list of escrow accounts with access to owner's tokens
const escrowAccounts = escrowAccess.getSome(owner)
// return result of check
return escrowAccounts.includes(account_id)
}

const escrow = escrowAccess.getSome(account_id)
return escrow == caller
}

// Get an individual owner by given `tokenId`.
Expand All @@ -136,7 +100,6 @@ export function mint_to(owner_id: AccountId): u64 {

// assign ownership
tokenToOwner.set(tokenId, owner_id)
escrowAccess.set(owner_id, [])

// increment and store the next tokenId
storage.set<u64>(TOTAL_SUPPLY, tokenId + 1)
Expand Down

0 comments on commit bd39400

Please sign in to comment.