Skip to content

Commit

Permalink
#102: Test allowances
Browse files Browse the repository at this point in the history
  • Loading branch information
mvandeberg committed Jul 25, 2024
1 parent a9acc4a commit e830301
Show file tree
Hide file tree
Showing 3 changed files with 322 additions and 158 deletions.
60 changes: 32 additions & 28 deletions contracts/vhp/assembly/Vhp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// Julian Gonzalez ([email protected])
// Koinos Group, Inc. ([email protected])

import { Arrays, authority, chain, error, kcs4, Protobuf, Storage, System, } from "@koinos/sdk-as";
import { Arrays, authority, Base58, chain, error, kcs4, Protobuf, Storage, System, } from "@koinos/sdk-as";
import { vhp } from "./proto/vhp";

/**
Expand Down Expand Up @@ -119,40 +119,44 @@ export class Vhp {
return new vhp.effective_balance_of_result(effectiveBalances.past_balances[0].balance);
}

allowance(args: kcs4.allowance_args): kcs4.allowance_result {
allowance(args: kcs4.allowance_arguments): kcs4.allowance_result {
System.require(args.owner != null, "allowance argument 'owner' cannot be null");
System.require(args.spender != null, "allowance argument 'spender' cannot be null");

let allowanceKeyBytes = Protobuf.encode(args, kcs4.allowance_args.encode);
return new kcs4.allowance_result(this.allowances.get(allowanceKeyBytes).value);
const key = new Uint8Array(50);
key.set(args.owner, 0);
key.set(args.spender, 25);

return new kcs4.allowance_result(this.allowances.get(key)!.value);
}

get_allowances(args: kcs4.get_allowances_args): kcs4.get_allowances_result {
get_allowances(args: kcs4.get_allowances_arguments): kcs4.get_allowances_result {
System.require(args.owner != null, 'owner cannot be null');

let allowanceKey = new kcs4.allowance_arguments(args.owner, args.spender);
let allowanceKeyBytes = Protobuf.encode(allowanceKey, kcs4.allowance_arguments.encode);
let result = new kcs4.get_allownaces_return(args.owner, []);
let key = new Uint8Array(50);
key.set(args.owner, 0);
key.set(args.start ? args.start : new Uint8Array(0), 25);

let result = new kcs4.get_allowances_result(args.owner, []);

for (let i = 0; i < args.limit; i++) {
const nextAllowance = args.descending
? this.allowances.getPrev(allowanceKeyBytes)
: this.allowances.getNext(allowanceKeyBytes);
? this.allowances.getPrev(key)
: this.allowances.getNext(key);

if (!nextAllowance) {
break;
}

allowanceKey = Protobuf.decode(nextAllowance.key!, kcs4.allowance_arguments.decode);

if (!Arrays.equal(allowanceKey.owner, args.owner)) {
if (!Arrays.equal(args.owner, nextAllowance.key!.slice(0, 25))) {
break;
}

result.allowances.push(
new kcs4.spender_value(allowanceKey.spender, nextAllowance.value)
new kcs4.spender_value(nextAllowance.key!.slice(25), nextAllowance.value.value)
);
allowanceKeyBytes = nextAllowance.key!;

key = nextAllowance.key!;
}

return result;
Expand Down Expand Up @@ -256,37 +260,37 @@ export class Vhp {
approve(args: kcs4.approve_arguments): kcs4.approve_result {
System.require(args.owner != null, "approve argument 'owner' cannot be null");
System.require(args.spender != null, "approve argument 'spender' cannot be null");
System.require(
System.checkAuthority(authority.authorization_type.contract_call, args.owner!),
'owner has not authorized approval',
error.error_code.authorization_failure
);
System.requireAuthority(authority.authorization_type.contract_call, args.owner);

let allowanceKey = new kcs4.allowance_arguments(args.owner!, args.spender!);
let allowanceKeyBytes = Protobuf.encode(allowanceKey, kcs4.allowance_arguments.encode);
this.allowances.put(allowanceKeyBytes, new vhp.balance_object(args.value));
const key = new Uint8Array(50);
key.set(args.owner, 0);
key.set(args.spender, 25);
this.allowances.put(key, new vhp.balance_object(args.value));

System.event(
"koinos.contracts.kcs4.approve",
Protobuf.encode(new kcs4.approve_event(args.owner, args.spender, args.value), kcs4.approve_event.encode),
[args.owner, args.spender]
);

return new kcs4.approve_result();
}

_check_authority(account: Uint8Array, amount: u64): boolean {
const caller = System.getCaller().caller;
if (caller && caller.length > 0) {
let allowanceKey = new kcs4.allowance_arguments(account, caller);
let allowanceKeyBytes = Protobuf.encode(allowanceKey, kcs4.allowance_arguments.encode);
const allowance = this.allowances.get(allowanceKeyBytes)!;
let key = new Uint8Array(50);
key.set(account, 0);
key.set(caller, 25);
const allowance = this.allowances.get(key)!;
if (allowance.value >= amount) {
allowance.value -= amount;
this.allowances.put(allowanceKeyBytes, allowance);
this.allowances.put(key, allowance);
return true;
}
}

return System.checkAuthority(authority.authorization_type.contract_call, account);
return System.checkAccountAuthority(account);
}

_increase_balance_by(balanceObj: vhp.effective_balance_object, blockHeight: u64, value: u64): void {
Expand Down
104 changes: 104 additions & 0 deletions contracts/vhp/assembly/__tests__/vhp.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const CONTRACT_ID = Base58.decode("1DQzuCcTKacbs9GGScRTU1Hc8BsyARTPqe");

const MOCK_ACCT1 = Base58.decode("1DQzuCcTKacbs9GGScRTU1Hc8BsyARTPqG");
const MOCK_ACCT2 = Base58.decode("1DQzuCcTKacbs9GGScRTU1Hc8BsyARTPqK");
const MOCK_ACCT3 = Base58.decode("1DQzuCcTKacbs9GGScRTU1Hc8BsyARTPqP");

const headBlock = new protocol.block(new Uint8Array(0), new protocol.block_header(new Uint8Array(0), 10));

Expand Down Expand Up @@ -40,6 +41,14 @@ describe("vhp", () => {
expect(res.value).toBe(8);
});

it("should get token info", () => {
const vhpContract = new Vhp();
const res = vhpContract.get_info();
expect(res.name).toBe("Virtual Hash Power");
expect(res.symbol).toBe("VHP");
expect(res.decimals).toBe(8);
});

it("should/not burn tokens", () => {
const vhpContract = new Vhp();
let callerData = new chain.caller_data();
Expand Down Expand Up @@ -578,4 +587,99 @@ describe("vhp", () => {
expect(Arrays.equal(transferEvent.to, MOCK_ACCT2)).toBe(true);
expect(transferEvent.value).toBe(10);
});

it("should approve", () => {
const vhpContract = new Vhp();

expect(vhpContract.allowance(new kcs4.allowance_arguments(MOCK_ACCT1, MOCK_ACCT2)).value).toBe(0);

const mockAcc1Auth = new MockVM.MockAuthority(authority.authorization_type.contract_call, MOCK_ACCT1, true);
MockVM.setAuthorities([mockAcc1Auth]);
vhpContract.approve(new kcs4.approve_arguments(MOCK_ACCT1, MOCK_ACCT2, 10));

expect(vhpContract.allowance(new kcs4.allowance_arguments(MOCK_ACCT1, MOCK_ACCT2)).value).toBe(10);

MockVM.setAuthorities([mockAcc1Auth]);
vhpContract.approve(new kcs4.approve_arguments(MOCK_ACCT1, MOCK_ACCT3, 20));

expect(vhpContract.allowance(new kcs4.allowance_arguments(MOCK_ACCT1, MOCK_ACCT3)).value).toBe(20);

MockVM.setAuthorities([new MockVM.MockAuthority(authority.authorization_type.contract_call, MOCK_ACCT2, true)]);
vhpContract.approve(new kcs4.approve_arguments(MOCK_ACCT2, MOCK_ACCT3, 30));

expect(vhpContract.allowance(new kcs4.allowance_arguments(MOCK_ACCT2, MOCK_ACCT3)).value).toBe(30);

// Tests basic allowances return
let allowances = vhpContract.get_allowances(new kcs4.get_allowances_arguments(MOCK_ACCT1, new Uint8Array(0), 10));
expect(Arrays.equal(allowances.owner, MOCK_ACCT1)).toBe(true);
expect(allowances.allowances.length).toBe(2);
expect(Arrays.equal(allowances.allowances[0].spender, MOCK_ACCT2)).toBe(true);
expect(allowances.allowances[0].value).toBe(10);
expect(Arrays.equal(allowances.allowances[1].spender, MOCK_ACCT3)).toBe(true);
expect(allowances.allowances[1].value).toBe(20);

// Tests allowances descending
allowances = vhpContract.get_allowances(new kcs4.get_allowances_arguments(MOCK_ACCT1, MOCK_ACCT3, 10, true));
expect(Arrays.equal(allowances.owner, MOCK_ACCT1)).toBe(true);
expect(allowances.allowances.length).toBe(1);
expect(Arrays.equal(allowances.allowances[0].spender, MOCK_ACCT2)).toBe(true);
expect(allowances.allowances[0].value).toBe(10);

// Tests allowances limit
allowances = vhpContract.get_allowances(new kcs4.get_allowances_arguments(MOCK_ACCT1, new Uint8Array(0), 1));
expect(Arrays.equal(allowances.owner, MOCK_ACCT1)).toBe(true);
expect(allowances.allowances.length).toBe(1);
expect(Arrays.equal(allowances.allowances[0].spender, MOCK_ACCT2)).toBe(true);
expect(allowances.allowances[0].value).toBe(10);

// Tests allowances pagination
allowances = vhpContract.get_allowances(new kcs4.get_allowances_arguments(MOCK_ACCT1, MOCK_ACCT2, 10));
expect(Arrays.equal(allowances.owner, MOCK_ACCT1)).toBe(true);
expect(allowances.allowances.length).toBe(1);
expect(Arrays.equal(allowances.allowances[0].spender, MOCK_ACCT3)).toBe(true);
expect(allowances.allowances[0].value).toBe(20);

// Tests another owner's allowances
allowances = vhpContract.get_allowances(new kcs4.get_allowances_arguments(MOCK_ACCT2, new Uint8Array(0), 10));
expect(Arrays.equal(allowances.owner, MOCK_ACCT2)).toBe(true);
expect(allowances.allowances.length).toBe(1);
expect(Arrays.equal(allowances.allowances[0].spender, MOCK_ACCT3)).toBe(true);
expect(allowances.allowances[0].value).toBe(30);
});

it("should require an approval", () => {
const vhpContract = new Vhp();

MockVM.setCaller(new chain.caller_data(MOCK_ACCT2, chain.privilege.kernel_mode));
vhpContract.mint(new kcs4.mint_arguments(MOCK_ACCT1, 100));

MockVM.setCaller(new chain.caller_data(MOCK_ACCT2, chain.privilege.user_mode));

// should not transfer because allowance does not exist
expect(() => {
const vhpContract = new Vhp();
vhpContract.transfer(new kcs4.transfer_arguments(MOCK_ACCT1, MOCK_ACCT2, 10));
}).toThrow();

expect(MockVM.getErrorMessage()).toBe("from has not authorized transfer");

// create allowance for 20 tokens
MockVM.setCaller(new chain.caller_data(new Uint8Array(0), chain.privilege.kernel_mode));
MockVM.setAuthorities([new MockVM.MockAuthority(authority.authorization_type.contract_call, MOCK_ACCT1, true)]);
vhpContract.approve(new kcs4.approve_arguments(MOCK_ACCT1, MOCK_ACCT2, 20));

MockVM.setCaller(new chain.caller_data(MOCK_ACCT2, chain.privilege.user_mode));

// should not transfer because allowance is too small
expect(() => {
const vhpContract = new Vhp();
vhpContract.transfer(new kcs4.transfer_arguments(MOCK_ACCT1, MOCK_ACCT2, 25));
}).toThrow();

// should transfer partial amount of allowance
vhpContract.transfer(new kcs4.transfer_arguments(MOCK_ACCT1, MOCK_ACCT2, 10));
expect(vhpContract.balance_of(new kcs4.balance_of_arguments(MOCK_ACCT1)).value).toBe(90);
expect(vhpContract.balance_of(new kcs4.balance_of_arguments(MOCK_ACCT2)).value).toBe(10);
expect(vhpContract.allowance(new kcs4.allowance_arguments(MOCK_ACCT1, MOCK_ACCT2)).value).toBe(10);
});
});
Loading

0 comments on commit e830301

Please sign in to comment.