namillum
Version:
Bubble Protocol SDK
593 lines (532 loc) • 19.1 kB
text/typescript
import { ScillaContract, initZilliqa, setAccount } from "hardhat-scilla-plugin";
import { Value } from "@zilliqa-js/contract";
import { assert, expect } from "chai";
import hre from "hardhat";
import { loadZilliqaHardhatObject } from "hardhat-scilla-plugin/dist/src/ZilliqaHardhatObject";
import * as zcrypto from "@zilliqa-js/crypto";
const newpk = zcrypto.schnorr.generatePrivateKey();
// these are all isolated server genesis accounts; if you're using devnet,
// you'll need to find three accounts and put their private keys here.
const adminpk =
"db11cfa086b92497c8ed5a4cc6edb3a5bfe3a640c43ffb9fc6aa0873c56f2ee3";
const userpk =
"e53d1c3edaffc7a7bab5418eb836cf75819a82872b4a1a0f1c7fcf5c3e020b89";
const privateKeys = [adminpk, userpk];
const network_url = "http://localhost:5555";
const chain_id = 1; // 222;
const setup = initZilliqa(network_url, chain_id, privateKeys);
const adminaddress = zcrypto.getAddressFromPrivateKey(adminpk).toLowerCase();
const useraddress = zcrypto.getAddressFromPrivateKey(userpk).toLowerCase();
const adminpubkey = zcrypto.getPubKeyFromPrivateKey(adminpk);
const userpubkey = zcrypto.getPubKeyFromPrivateKey(userpk);
describe("BurnAnyTokenZRC2Test", () => {
var burncontract: ScillaContract;
var zrc2contract: ScillaContract;
beforeEach(async () => {
setAccount(0);
burncontract = await hre.deployScillaContract(
"BurnTokensAnyZRC2",
adminaddress
);
zrc2contract = await hre.deployScillaContract(
"FungibleToken",
adminaddress,
"TestToken",
"TT",
"0",
"100000"
);
});
it("Should deploy correctly", async () => {
expect(burncontract.address).to.be.properAddress;
expect(zrc2contract.address).to.be.properAddress;
});
it("Should be possible to set a burn allowance", async () => {
setAccount(1);
// This asks us to set up a burn allowance for account 1 from zrc2contract.address for 100 tokens
{
await burncontract.UpdateBurnAllowance(zrc2contract.address, 100);
const allowed_burn = (await burncontract.allow_burn_tokens())[
zrc2contract.address!.toLowerCase()
][useraddress];
assert(allowed_burn == 100);
}
// Now set it back to 0.
{
await burncontract.UpdateBurnAllowance(zrc2contract.address, 0);
const allowed_burn = (await burncontract.allow_burn_tokens())[
zrc2contract.address!.toLowerCase()
][useraddress];
assert(allowed_burn == 0);
}
});
it("Should be possible to burn tokens", async () => {
setAccount(0);
await zrc2contract.Transfer(useraddress, 100);
await burncontract.ChangeBurnCancelBlocks(0);
setAccount(1);
// We'll check that the events fire properly here so as not to have another test
// case (and thus speed up tests)
{
let tx = await burncontract.UpdateBurnAllowance(
zrc2contract.address,
100
);
expect(tx).to.eventLogWithParams(
"UpdateBurnAllowanceSuccess",
{
type: "ByStr20",
value: zrc2contract.address.toLowerCase(),
vname: "token_address",
},
{
type: "Uint128",
value: { _hex: "0x64", _isBigNumber: true },
vname: "token_amount",
},
{ type: "ByStr20", value: useraddress, vname: "updated_by" }
);
}
await zrc2contract.Transfer(burncontract.address!, 100);
// Burns can be finalised by anyone
// @todo when we have ZIL transfers, make this a separate account - can't at the
// moment 'cos it won't have enough gas.
setAccount(0);
{
let tx = await burncontract.FinaliseBurn(
zrc2contract.address,
useraddress
);
expect(tx).to.eventLogWithParams(
"TokenBurnConfirmation",
{
type: "ByStr20",
value: zrc2contract.address.toLowerCase(),
vname: "token_address",
},
{
type: "Uint128",
value: { _hex: "0x64", _isBigNumber: true },
vname: "token_amount",
},
{ type: "ByStr20", value: useraddress, vname: "burnt_by" }
);
}
const pending_burn = await burncontract.pending_burn();
const balances = await zrc2contract.balances();
assert(
pending_burn[useraddress] == null,
"The user should have no pending burns"
);
assert(balances[useraddress] == 0, "The user should have no tokens");
assert(
balances[burncontract.address!.toLowerCase()] == 100,
"The contract should have 100 tokens"
);
assert(
balances[adminaddress] == 99900,
"The admin must have all unburned tokens"
);
});
it("Should be possible to undo your burn before the timeout expires", async () => {
setAccount(0);
await zrc2contract.Transfer(useraddress, 100);
let burnCancelTx = await burncontract.ChangeBurnCancelBlocks(20);
expect(burnCancelTx).to.eventLogWithParams("ChangeBurnCancelBlocks", {
type: "Uint32",
vname: "new_cancel_blocks",
value: 20,
});
setAccount(1);
await burncontract.UpdateBurnAllowance(zrc2contract.address, 100);
await zrc2contract.Transfer(burncontract.address!, 100);
let burnTx = await burncontract.CancelBurn(zrc2contract.address);
expect(burnTx).to.eventLogWithParams(
"CancelBurn",
{
type: "ByStr20",
value: zrc2contract.address.toLowerCase(),
vname: "token",
},
{
type: "ByStr20",
value: hre.zilliqa.getAccounts()[1].address.toLowerCase(),
vname: "sender",
},
{
type: "Uint128",
vname: "token_amount",
value: {
_hex: "0x64",
_isBigNumber: true,
},
},
{
type: "Bool",
vname: "success",
value: true,
}
);
const pending_burn = await burncontract.pending_burn();
const balances = await zrc2contract.balances();
assert(
pending_burn[useraddress] == null,
"The user should have no pending burns"
);
assert(balances[useraddress] == 100, "The user should have 100 tokens");
assert(
balances[burncontract.address!.toLowerCase()] == 0,
"The contract should have 100 tokens"
);
assert(
balances[adminaddress] == 99900,
"The admin must have all unburned tokens"
);
// Try to finalise the burn...
// Burns can be actioned by anyone
setAccount(0);
await burncontract.FinaliseBurn(zrc2contract.address, useraddress);
assert(
pending_burn[useraddress] == null,
"The user should have no pending burns"
);
assert(balances[useraddress] == 100, "The user should have 100 tokens");
assert(
balances[burncontract.address!.toLowerCase()] == 0,
"The contract should have 100 tokens"
);
assert(
balances[adminaddress] == 99900,
"The admin must have all unburned tokens"
);
});
it("Should not be possible to undo a burn after the timeout expires", async () => {
setAccount(0);
await zrc2contract.Transfer(useraddress, 100);
// This is a hack because it is hard (and takes a long time) to allow blocks to pass.
await burncontract.ChangeBurnCancelBlocks(0);
setAccount(1);
await burncontract.UpdateBurnAllowance(zrc2contract.address, 100);
await zrc2contract.Transfer(burncontract.address!, 100);
{
let burnTx = await burncontract.CancelBurn(zrc2contract.address);
expect(burnTx).to.eventLogWithParams(
"CancelBurn",
{
type: "ByStr20",
value: zrc2contract.address.toLowerCase(),
vname: "token",
},
{
type: "ByStr20",
value: hre.zilliqa.getAccounts()[1].address.toLowerCase(),
vname: "sender",
},
{
type: "Uint128",
vname: "token_amount",
value: {
_hex: "0x00",
_isBigNumber: true,
},
},
{
type: "Bool",
vname: "success",
value: false,
}
);
}
const pending_burn = await burncontract.pending_burn();
const balances = await zrc2contract.balances();
assert(
pending_burn[useraddress] == null,
"The user should have no pending burns"
);
assert(balances[useraddress] == 0, "The user should have no tokens");
assert(
balances[burncontract.address!.toLowerCase()] == 100,
"The contract should have 100 tokens"
);
assert(
balances[adminaddress] == 99900,
"The admin must have all unburned tokens"
);
});
it("Should be possible to burn via allowances", async () => {
setAccount(0);
await zrc2contract.Transfer(useraddress, 100);
await burncontract.ChangeBurnCancelBlocks(0);
// This is rather dodgy, because we're reusing the admin address.
// @todo when we have transfers, use a second user address for this.
setAccount(1);
await zrc2contract.IncreaseAllowance(adminaddress, 100);
await burncontract.UpdateBurnAllowance(zrc2contract.address, 100);
// Now the allowance is set up, we should be able to burn the tokens by
// sending them via the allowance,
setAccount(0);
await zrc2contract.TransferFrom(useraddress, burncontract.address, 100);
await burncontract.FinaliseBurn(zrc2contract.address, useraddress);
const pending_burn = await burncontract.pending_burn();
const balances = await zrc2contract.balances();
// This should have worked ..
assert(
pending_burn[useraddress] == null,
"The user should have no pending burns"
);
assert(balances[useraddress] == 0, "The user should have no tokens");
assert(
balances[burncontract.address!.toLowerCase()] == 100,
"The contract should have 100 tokens"
);
assert(
balances[adminaddress] == 99900,
"The admin must have all unburned tokens"
);
});
it("Should not be possible to burn more tokens than the allowance", async () => {
setAccount(0);
await zrc2contract.Transfer(useraddress, 100);
await burncontract.ChangeBurnCancelBlocks(0);
setAccount(1);
// We'll check that the events fire properly here so as not to have another test
// case (and thus speed up tests)
{
let tx = await burncontract.UpdateBurnAllowance(zrc2contract.address, 50);
expect(tx).to.eventLogWithParams(
"UpdateBurnAllowanceSuccess",
{
type: "ByStr20",
value: zrc2contract.address.toLowerCase(),
vname: "token_address",
},
{
type: "Uint128",
value: { _hex: "0x32", _isBigNumber: true },
vname: "token_amount",
},
{ type: "ByStr20", value: useraddress, vname: "updated_by" }
);
}
{
let tx = await zrc2contract.Transfer(burncontract.address!, 100);
expect(tx).to.nested.include({ "receipt.accepted": false });
expect(tx).to.nested.include({ "receipt.errors.1[0]": 7 });
}
// Burn finalisation should still work.
setAccount(0);
{
await burncontract.FinaliseBurn(zrc2contract.address, useraddress);
}
// The user should still have their tokens
const pending_burn = await burncontract.pending_burn();
const balances = await zrc2contract.balances();
assert(
pending_burn[useraddress] == null,
"The user should have no pending burns"
);
assert(balances[useraddress] == 100, "The user should have 100 tokens");
// 0 - in fact, this will be undefined
assert(
balances[burncontract.address!.toLowerCase()] === undefined,
"The contract should have 0 tokens"
);
assert(
balances[adminaddress] == 99900,
"The admin must have all unburned tokens"
);
});
it("Should be possible to hand over control of the contract", async () => {
setAccount(0);
// First, check that you can abort a failed ownership request.
await burncontract.SetContractOwnershipRecipient(zrc2contract.address);
setAccount(1);
// We should fail to take ownership..
{
let tx = await burncontract.AcceptContractOwnership();
expect(tx).to.nested.include({ "receipt.success": false });
expect(tx).to.nested.include({ "receipt.errors.0[0]": 7 });
}
// Now do it right ..
setAccount(0);
await burncontract.SetContractOwnershipRecipient(useraddress);
setAccount(1);
await burncontract.AcceptContractOwnership();
const owner = await burncontract.contract_owner();
const recipient = await burncontract.contract_ownership_recipient();
assert(owner === useraddress.toLowerCase());
assert(recipient === "0x0000000000000000000000000000000000000000");
});
it("Should be possible to pause a single ZRC2", async () => {
setAccount(0);
await zrc2contract.Transfer(useraddress, 100);
// You can only pause if you're the owner.
setAccount(1);
{
let tx = await burncontract.Pause(zrc2contract.address);
expect(tx).to.nested.include({ "receipt.success": false });
expect(tx).to.nested.include({ "receipt.errors.0[0]": 7 });
}
// Set up some allowances so that we might succeed later on.
await burncontract.UpdateBurnAllowance(zrc2contract.address, 50);
await zrc2contract.IncreaseAllowance(adminaddress, 50);
{
let paused = await burncontract.paused();
let paused_zrc2 = await burncontract.paused_zrc2();
// Annoyingly, can't check this with current hardhat-scilla-plugin
expect(paused["constructor"]).to.equal("False");
expect(paused_zrc2).to.be.empty;
}
{
// .. so be the owner
setAccount(0);
await burncontract.Pause(zrc2contract.address);
let paused = await burncontract.paused();
let paused_zrc2 = await burncontract.paused_zrc2();
let zca = zrc2contract.address.toLowerCase();
expect(paused_zrc2[zca]["constructor"]).to.equal("True");
}
setAccount(1);
{
let tx = await burncontract.UpdateBurnAllowance(
zrc2contract.address,
100
);
expect(tx).to.nested.include({ "receipt.success": false });
expect(tx).to.nested.include({ "receipt.errors.0[0]": 7 });
}
{
let tx = await zrc2contract.Transfer(burncontract.address!, 50);
//console.log(`xTy ${JSON.stringify(tx)}`);
expect(tx).to.nested.include({ "receipt.success": false });
expect(tx).to.nested.include({ "receipt.errors.1[0]": 7 });
}
setAccount(0);
{
let tx = await zrc2contract.TransferFrom(
useraddress,
burncontract.address,
50
);
expect(tx).to.nested.include({ "receipt.success": false });
expect(tx).to.nested.include({ "receipt.errors.1[0]": 7 });
}
setAccount(1);
// Cancel is in fact allowed when a particular ZRC2 is paused.
// See the README.md
{
let tx = await burncontract.CancelBurn(zrc2contract.address);
expect(tx).to.nested.include({ "receipt.success": true });
}
{
let tx = await burncontract.FinaliseBurn(
zrc2contract.address,
useraddress
);
expect(tx).to.nested.include({ "receipt.success": false });
expect(tx).to.nested.include({ "receipt.errors.0[0]": 7 });
}
// You need to be admin to unpause
setAccount(1);
{
let tx = await burncontract.UnPause(zrc2contract.address);
expect(tx).to.nested.include({ "receipt.success": false });
expect(tx).to.nested.include({ "receipt.errors.0[0]": 7 });
}
setAccount(0);
await burncontract.UnPause(zrc2contract.address);
{
let paused = await burncontract.paused();
let paused_zrc2 = await burncontract.paused_zrc2();
// Annoyingly, can't check this with current hardhat-scilla-plugin
expect(paused["constructor"]).to.equal("False");
expect(paused_zrc2).to.be.empty;
}
});
it("Should be possible to pause everything", async () => {
setAccount(0);
await zrc2contract.Transfer(useraddress, 100);
// You can only pause if you're the owner.
setAccount(1);
{
let tx = await burncontract.PauseAll();
expect(tx).to.nested.include({ "receipt.success": false });
expect(tx).to.nested.include({ "receipt.errors.0[0]": 7 });
}
// Set up some allowances so that we might succeed later on.
await burncontract.UpdateBurnAllowance(zrc2contract.address, 50);
await zrc2contract.IncreaseAllowance(adminaddress, 50);
{
let paused = await burncontract.paused();
let paused_zrc2 = await burncontract.paused_zrc2();
// Annoyingly, can't check this with current hardhat-scilla-plugin
expect(paused["constructor"]).to.equal("False");
expect(paused_zrc2).to.be.empty;
}
{
// .. so be the owner
setAccount(0);
await burncontract.PauseAll();
let paused = await burncontract.paused();
let paused_zrc2 = await burncontract.paused_zrc2();
let zca = zrc2contract.address.toLowerCase();
expect(paused["constructor"]).to.equal("True");
expect(paused_zrc2).to.be.empty;
}
setAccount(1);
{
let tx = await burncontract.UpdateBurnAllowance(
zrc2contract.address,
100
);
expect(tx).to.nested.include({ "receipt.success": false });
expect(tx).to.nested.include({ "receipt.errors.0[0]": 7 });
}
{
let tx = await zrc2contract.Transfer(burncontract.address!, 50);
//console.log(`xTy ${JSON.stringify(tx)}`);
expect(tx).to.nested.include({ "receipt.success": false });
expect(tx).to.nested.include({ "receipt.errors.1[0]": 7 });
}
setAccount(0);
{
let tx = await zrc2contract.TransferFrom(
useraddress,
burncontract.address,
50
);
expect(tx).to.nested.include({ "receipt.success": false });
expect(tx).to.nested.include({ "receipt.errors.1[0]": 7 });
}
setAccount(1);
{
let tx = await burncontract.CancelBurn(zrc2contract.address);
expect(tx).to.nested.include({ "receipt.success": false });
expect(tx).to.nested.include({ "receipt.errors.0[0]": 7 });
}
{
let tx = await burncontract.FinaliseBurn(
zrc2contract.address,
useraddress
);
expect(tx).to.nested.include({ "receipt.success": false });
expect(tx).to.nested.include({ "receipt.errors.0[0]": 7 });
}
// You need to be admin to unpause
setAccount(1);
{
let tx = await burncontract.UnPause(zrc2contract.address);
expect(tx).to.nested.include({ "receipt.success": false });
expect(tx).to.nested.include({ "receipt.errors.0[0]": 7 });
}
setAccount(0);
await burncontract.UnPauseAll();
{
let paused = await burncontract.paused();
let paused_zrc2 = await burncontract.paused_zrc2();
// Annoyingly, can't check this with current hardhat-scilla-plugin
expect(paused["constructor"]).to.equal("False");
expect(paused_zrc2).to.be.empty;
}
});
});