5273 lines
151 KiB
JavaScript
5273 lines
151 KiB
JavaScript
const jsonfile = require("jsonfile");
|
|
const elliptic = require("elliptic");
|
|
const { assert } = require("chai");
|
|
|
|
const TokenImplementation = artifacts.require("TokenImplementation");
|
|
|
|
const TokenSaleConductor = artifacts.require("TokenSaleConductor");
|
|
const TokenSaleContributor = artifacts.require("TokenSaleContributor");
|
|
const MockConductorImplementation = artifacts.require(
|
|
"MockConductorImplementation"
|
|
);
|
|
const MockContributorImplementation = artifacts.require(
|
|
"MockContributorImplementation"
|
|
);
|
|
const ICCOStructs = artifacts.require("ICCOStructs");
|
|
const ConductorImplementation = artifacts.require("ConductorImplementation");
|
|
const ContributorImplementation = artifacts.require(
|
|
"ContributorImplementation"
|
|
);
|
|
|
|
// library upgrade test
|
|
const MockICCOStructs = artifacts.require("MockICCOStructs");
|
|
const MockConductorImplementation2 = artifacts.require(
|
|
"MockConductorImplementation2"
|
|
);
|
|
const MockContributorImplementation2 = artifacts.require(
|
|
"MockContributorImplementation2"
|
|
);
|
|
|
|
const testSigner1PK =
|
|
"cfb12303a19cde580bb4dd771639b0d26bc68353645571a8cff516ab2ee113a0";
|
|
const kycSignerPK =
|
|
"b0057716d5917badaf911b193b12b910811c1497b5bada8d7711f758981c3773";
|
|
|
|
const WormholeImplementationFullABI = jsonfile.readFileSync(
|
|
"wormhole/ethereum/build/contracts/Implementation.json"
|
|
).abi;
|
|
const ConductorImplementationFullABI = jsonfile.readFileSync(
|
|
"build/contracts/ConductorImplementation.json"
|
|
).abi;
|
|
const ContributorImplementationFullABI = jsonfile.readFileSync(
|
|
"build/contracts/ContributorImplementation.json"
|
|
).abi;
|
|
const ConsistencyLevel = process.env.CONSISTENCY_LEVEL;
|
|
|
|
// global variables
|
|
const TEST_CHAIN_ID = "2";
|
|
const GAS_LIMIT = "3000000";
|
|
|
|
const ethereumRootPath = `${__dirname}/..`;
|
|
const config = require(`${ethereumRootPath}/icco_deployment_config.js`)
|
|
.development;
|
|
|
|
// add a test that makes sure we pass the right token info for localTokenAddress lookup
|
|
|
|
contract("ICCO", function(accounts) {
|
|
const WORMHOLE = new web3.eth.Contract(
|
|
WormholeImplementationFullABI,
|
|
config.wormhole
|
|
);
|
|
const CONDUCTOR_BYTES32_ADDRESS =
|
|
"0x000000000000000000000000" + TokenSaleConductor.address.substr(2);
|
|
|
|
it("conductor should be initialized with the correct values", async function() {
|
|
console.log(
|
|
"\n -------------------------- Initialization and Upgrades --------------------------"
|
|
);
|
|
const initialized = new web3.eth.Contract(
|
|
ConductorImplementationFullABI,
|
|
TokenSaleConductor.address
|
|
);
|
|
|
|
// chain id
|
|
const chainId = await initialized.methods.chainId().call();
|
|
assert.equal(chainId, TEST_CHAIN_ID);
|
|
|
|
// wormhole
|
|
const WORMHOLE = await initialized.methods.wormhole().call();
|
|
assert.equal(WORMHOLE, config.wormhole);
|
|
|
|
// tokenBridge
|
|
const tokenbridge = await initialized.methods.tokenBridge().call();
|
|
assert.equal(tokenbridge, config.tokenBridge);
|
|
});
|
|
|
|
it("contributor should be initialized with the correct values", async function() {
|
|
const initialized = new web3.eth.Contract(
|
|
ContributorImplementationFullABI,
|
|
TokenSaleContributor.address
|
|
);
|
|
|
|
// chain id
|
|
const chainId = await initialized.methods.chainId().call();
|
|
assert.equal(chainId, TEST_CHAIN_ID);
|
|
|
|
// conductor
|
|
const conductorChainId = await initialized.methods
|
|
.conductorChainId()
|
|
.call();
|
|
assert.equal(conductorChainId, TEST_CHAIN_ID);
|
|
const conductorContract = await initialized.methods
|
|
.conductorContract()
|
|
.call();
|
|
assert.equal(
|
|
conductorContract.substr(26).toLowerCase(),
|
|
TokenSaleConductor.address.substr(2).toLowerCase()
|
|
);
|
|
|
|
// wormhole
|
|
const WORMHOLE = await initialized.methods.wormhole().call();
|
|
assert.equal(WORMHOLE, config.wormhole);
|
|
|
|
// tokenBridge
|
|
const tokenbridge = await initialized.methods.tokenBridge().call();
|
|
assert.equal(tokenbridge, config.tokenBridge);
|
|
});
|
|
|
|
it("conductor should register a contributor implementation correctly", async function() {
|
|
const contributorAddress = web3.eth.abi.encodeParameter(
|
|
"bytes32",
|
|
"0x000000000000000000000000" + TokenSaleContributor.address.substr(2)
|
|
);
|
|
|
|
const initialized = new web3.eth.Contract(
|
|
ConductorImplementationFullABI,
|
|
TokenSaleConductor.address
|
|
);
|
|
|
|
let before = await initialized.methods
|
|
.contributorContracts(TEST_CHAIN_ID)
|
|
.call();
|
|
|
|
assert.equal(
|
|
before,
|
|
"0x0000000000000000000000000000000000000000000000000000000000000000"
|
|
);
|
|
|
|
// attempt to register a chain from non-owner account
|
|
let failed = false;
|
|
try {
|
|
await initialized.methods
|
|
.registerChain(TEST_CHAIN_ID, contributorAddress, contributorAddress)
|
|
.send({
|
|
value: 0,
|
|
from: accounts[1],
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
} catch (e) {
|
|
assert.equal(
|
|
e.message,
|
|
"Returned error: VM Exception while processing transaction: revert caller is not the owner"
|
|
);
|
|
failed = true;
|
|
}
|
|
|
|
assert.ok(failed);
|
|
|
|
await initialized.methods
|
|
.registerChain(TEST_CHAIN_ID, contributorAddress, contributorAddress)
|
|
.send({
|
|
value: 0,
|
|
from: accounts[0],
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
let after = await initialized.methods
|
|
.contributorContracts(TEST_CHAIN_ID)
|
|
.call();
|
|
|
|
assert.equal(
|
|
after.substr(26).toLowerCase(),
|
|
TokenSaleContributor.address.substr(2).toLowerCase()
|
|
);
|
|
|
|
// attempt to register a contributor a second time
|
|
failed = false;
|
|
try {
|
|
await initialized.methods
|
|
.registerChain(TEST_CHAIN_ID, contributorAddress, contributorAddress)
|
|
.send({
|
|
value: 0,
|
|
from: accounts[0],
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
} catch (e) {
|
|
assert.equal(
|
|
e.message,
|
|
"Returned error: VM Exception while processing transaction: revert chain already registered"
|
|
);
|
|
failed = true;
|
|
}
|
|
});
|
|
|
|
it("conductor should accept a valid upgrade", async function() {
|
|
const initialized = new web3.eth.Contract(
|
|
ConductorImplementationFullABI,
|
|
TokenSaleConductor.address
|
|
);
|
|
|
|
// deploy mock contracts and link ICCOStructs library
|
|
const structs = await ICCOStructs.new();
|
|
await MockConductorImplementation.link(structs, structs.address);
|
|
const mock = await MockConductorImplementation.new();
|
|
|
|
// attempt to upgrade a chain from non-owner account
|
|
let failed = false;
|
|
try {
|
|
await initialized.methods.upgrade(TEST_CHAIN_ID, mock.address).send({
|
|
value: 0,
|
|
from: accounts[1],
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
} catch (e) {
|
|
assert.equal(
|
|
e.message,
|
|
"Returned error: VM Exception while processing transaction: revert caller is not the owner"
|
|
);
|
|
failed = true;
|
|
}
|
|
|
|
assert.ok(failed);
|
|
|
|
// confirm that the implementation address changes
|
|
let before = await web3.eth.getStorageAt(
|
|
TokenSaleConductor.address,
|
|
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc"
|
|
);
|
|
|
|
assert.equal(
|
|
before.toLowerCase(),
|
|
ConductorImplementation.address.toLowerCase()
|
|
);
|
|
|
|
await initialized.methods.upgrade(TEST_CHAIN_ID, mock.address).send({
|
|
value: 0,
|
|
from: accounts[0],
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
let after = await web3.eth.getStorageAt(
|
|
TokenSaleConductor.address,
|
|
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc"
|
|
);
|
|
|
|
assert.equal(after.toLowerCase(), mock.address.toLowerCase());
|
|
|
|
const mockImpl = new web3.eth.Contract(
|
|
MockConductorImplementation.abi,
|
|
TokenSaleConductor.address
|
|
);
|
|
|
|
let isUpgraded = await mockImpl.methods
|
|
.testNewImplementationActive()
|
|
.call();
|
|
|
|
assert.ok(isUpgraded);
|
|
});
|
|
|
|
it("contributor should accept a valid upgrade", async function() {
|
|
const initialized = new web3.eth.Contract(
|
|
ContributorImplementationFullABI,
|
|
TokenSaleContributor.address
|
|
);
|
|
|
|
// deploy mock contracts and link ICCOStructs library
|
|
const structs = await ICCOStructs.new();
|
|
await MockContributorImplementation.link(structs, structs.address);
|
|
const mock = await MockContributorImplementation.new();
|
|
|
|
let failed = false;
|
|
try {
|
|
await initialized.methods.upgrade(TEST_CHAIN_ID, mock.address).send({
|
|
value: 0,
|
|
from: accounts[1],
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
} catch (e) {
|
|
assert.equal(
|
|
e.message,
|
|
"Returned error: VM Exception while processing transaction: revert caller is not the owner"
|
|
);
|
|
failed = true;
|
|
}
|
|
|
|
assert.ok(failed);
|
|
|
|
// confirm that the implementation address changes
|
|
let before = await web3.eth.getStorageAt(
|
|
TokenSaleContributor.address,
|
|
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc"
|
|
);
|
|
|
|
assert.equal(
|
|
before.toLowerCase(),
|
|
ContributorImplementation.address.toLowerCase()
|
|
);
|
|
|
|
await initialized.methods.upgrade(TEST_CHAIN_ID, mock.address).send({
|
|
value: 0,
|
|
from: accounts[0],
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
let after = await web3.eth.getStorageAt(
|
|
TokenSaleContributor.address,
|
|
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc"
|
|
);
|
|
|
|
assert.equal(after.toLowerCase(), mock.address.toLowerCase());
|
|
|
|
const mockImpl = new web3.eth.Contract(
|
|
MockContributorImplementation.abi,
|
|
TokenSaleContributor.address
|
|
);
|
|
|
|
let isUpgraded = await mockImpl.methods
|
|
.testNewImplementationActive()
|
|
.call();
|
|
|
|
assert.ok(isUpgraded);
|
|
});
|
|
|
|
it("contributor should should the allow owner to update authority correctly", async function() {
|
|
// test variables
|
|
const currentAuthority = config.authority; // initalized value for authority
|
|
const newAuthority = accounts[3];
|
|
|
|
const initialized = new web3.eth.Contract(
|
|
ContributorImplementationFullABI,
|
|
TokenSaleContributor.address
|
|
);
|
|
|
|
// update the kyc authority
|
|
await initialized.methods
|
|
.updateAuthority(TEST_CHAIN_ID, newAuthority)
|
|
.send({
|
|
value: "0",
|
|
from: accounts[0], // contract owner
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
// check getters after the action
|
|
const contributorAuthorityAfterUpdate = await initialized.methods
|
|
.authority()
|
|
.call();
|
|
|
|
assert.equal(contributorAuthorityAfterUpdate, newAuthority);
|
|
|
|
// make sure only the Contributor owner can change authority
|
|
let failed = false;
|
|
try {
|
|
await initialized.methods
|
|
.updateAuthority(TEST_CHAIN_ID, currentAuthority)
|
|
.send({
|
|
value: "0",
|
|
from: accounts[1], // different account
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
} catch (e) {
|
|
assert.equal(
|
|
e.message,
|
|
"Returned error: VM Exception while processing transaction: revert caller is not the owner"
|
|
);
|
|
failed = true;
|
|
}
|
|
|
|
assert.ok(failed);
|
|
|
|
// revert the autority change
|
|
await initialized.methods
|
|
.updateAuthority(TEST_CHAIN_ID, currentAuthority)
|
|
.send({
|
|
value: "0",
|
|
from: accounts[0], // contract owner
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
});
|
|
|
|
it("conductor and contributor should allow the owner to update consistencyLevel", async function() {
|
|
// test variables
|
|
const initializedConsistencyLevel = config.consistencyLevel;
|
|
const updatedConsistencyLevel = "1";
|
|
|
|
const contributorContract = new web3.eth.Contract(
|
|
ContributorImplementationFullABI,
|
|
TokenSaleContributor.address
|
|
);
|
|
const conductorContract = new web3.eth.Contract(
|
|
ConductorImplementationFullABI,
|
|
TokenSaleConductor.address
|
|
);
|
|
|
|
// update the consistency level
|
|
await contributorContract.methods
|
|
.updateConsistencyLevel(TEST_CHAIN_ID, updatedConsistencyLevel)
|
|
.send({
|
|
value: "0",
|
|
from: accounts[0], // contract owner
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
await conductorContract.methods
|
|
.updateConsistencyLevel(TEST_CHAIN_ID, updatedConsistencyLevel)
|
|
.send({
|
|
value: "0",
|
|
from: accounts[0], // contract owner
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
// check getters after the action
|
|
const contributorConsistencyLevelAfter = await contributorContract.methods
|
|
.consistencyLevel()
|
|
.call();
|
|
const conductorConsistencyLevelAfter = await conductorContract.methods
|
|
.consistencyLevel()
|
|
.call();
|
|
|
|
assert.equal(contributorConsistencyLevelAfter, updatedConsistencyLevel);
|
|
assert.equal(conductorConsistencyLevelAfter, updatedConsistencyLevel);
|
|
|
|
// revert consistencyLevel back to initialized value
|
|
// update the consistency level
|
|
await contributorContract.methods
|
|
.updateConsistencyLevel(TEST_CHAIN_ID, initializedConsistencyLevel)
|
|
.send({
|
|
value: "0",
|
|
from: accounts[0], // contract owner
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
await conductorContract.methods
|
|
.updateConsistencyLevel(TEST_CHAIN_ID, initializedConsistencyLevel)
|
|
.send({
|
|
value: "0",
|
|
from: accounts[0], // contract owner
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
// make sure only the Contributor owner can change consistencyLevel
|
|
let contributorFailed = false;
|
|
try {
|
|
await contributorContract.methods
|
|
.updateConsistencyLevel(TEST_CHAIN_ID, initializedConsistencyLevel)
|
|
.send({
|
|
value: "0",
|
|
from: accounts[1], // different account
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
} catch (e) {
|
|
assert.equal(
|
|
e.message,
|
|
"Returned error: VM Exception while processing transaction: revert caller is not the owner"
|
|
);
|
|
contributorFailed = true;
|
|
}
|
|
|
|
// make sure only the Conductor owner can change consistencyLevel
|
|
conductorFailed = false;
|
|
try {
|
|
await conductorContract.methods
|
|
.updateConsistencyLevel(TEST_CHAIN_ID, initializedConsistencyLevel)
|
|
.send({
|
|
value: "0",
|
|
from: accounts[1], // different account
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
} catch (e) {
|
|
assert.equal(
|
|
e.message,
|
|
"Returned error: VM Exception while processing transaction: revert caller is not the owner"
|
|
);
|
|
conductorFailed = true;
|
|
}
|
|
|
|
assert.ok(contributorFailed);
|
|
assert.ok(conductorFailed);
|
|
});
|
|
|
|
it("conductor and contributor should allow the owner to transfer ownership", async function() {
|
|
// test variables
|
|
const currentOwner = accounts[0];
|
|
const newOwner = accounts[1];
|
|
|
|
const contributorContract = new web3.eth.Contract(
|
|
ContributorImplementationFullABI,
|
|
TokenSaleContributor.address
|
|
);
|
|
const conductorContract = new web3.eth.Contract(
|
|
ConductorImplementationFullABI,
|
|
TokenSaleConductor.address
|
|
);
|
|
|
|
// transfer ownership
|
|
await contributorContract.methods
|
|
.transferOwnership(TEST_CHAIN_ID, newOwner)
|
|
.send({
|
|
value: "0",
|
|
from: currentOwner, // contract owner
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
await conductorContract.methods
|
|
.transferOwnership(TEST_CHAIN_ID, newOwner)
|
|
.send({
|
|
value: "0",
|
|
from: currentOwner, // contract owner
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
// check getters after the action
|
|
let contributorOwner = await contributorContract.methods.owner().call();
|
|
let conductorOwner = await conductorContract.methods.owner().call();
|
|
|
|
assert.equal(contributorOwner, newOwner);
|
|
assert.equal(conductorOwner, newOwner);
|
|
|
|
// make sure only the owner can transfer ownership
|
|
let contributorFailed = false;
|
|
try {
|
|
await contributorContract.methods
|
|
.transferOwnership(TEST_CHAIN_ID, currentOwner)
|
|
.send({
|
|
value: "0",
|
|
from: currentOwner, // no longer the current owner
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
} catch (e) {
|
|
assert.equal(
|
|
e.message,
|
|
"Returned error: VM Exception while processing transaction: revert caller is not the owner"
|
|
);
|
|
contributorFailed = true;
|
|
}
|
|
conductorFailed = false;
|
|
try {
|
|
await conductorContract.methods
|
|
.transferOwnership(TEST_CHAIN_ID, currentOwner)
|
|
.send({
|
|
value: "0",
|
|
from: currentOwner, // no longer the current owner
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
} catch (e) {
|
|
assert.equal(
|
|
e.message,
|
|
"Returned error: VM Exception while processing transaction: revert caller is not the owner"
|
|
);
|
|
conductorFailed = true;
|
|
}
|
|
|
|
assert.ok(contributorFailed);
|
|
assert.ok(conductorFailed);
|
|
|
|
// revert ownership back to currentOwner
|
|
await contributorContract.methods
|
|
.transferOwnership(TEST_CHAIN_ID, currentOwner)
|
|
.send({
|
|
value: "0",
|
|
from: newOwner,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
await conductorContract.methods
|
|
.transferOwnership(TEST_CHAIN_ID, currentOwner)
|
|
.send({
|
|
value: "0",
|
|
from: newOwner,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
// check getters before the action
|
|
contributorOwner = await contributorContract.methods.owner().call();
|
|
conductorOwner = await conductorContract.methods.owner().call();
|
|
|
|
assert.equal(contributorOwner, currentOwner);
|
|
assert.equal(conductorOwner, currentOwner);
|
|
});
|
|
|
|
// global sale test variables
|
|
let SOLD_TOKEN;
|
|
let SOLD_TOKEN_BYTES32_ADDRESS;
|
|
let CONTRIBUTED_TOKEN_ONE;
|
|
let CONTRIBUTED_TOKEN_TWO;
|
|
let SOLD_TOKEN_DECIMALS;
|
|
const SELLER = accounts[0];
|
|
const BUYER_ONE = accounts[1];
|
|
const BUYER_TWO = accounts[2];
|
|
|
|
it("mint one token to sell, two to buy", async function() {
|
|
// test variables
|
|
const tokenDecimals = 18;
|
|
const mintAccount = accounts[0];
|
|
const tokenSequence = 0; // set to 0 for the test
|
|
const tokenChainId = 0; // set to 0 for the test
|
|
const nativeContractAddress = "0x00"; // set to 0 for the test
|
|
|
|
// token amounts to mint
|
|
const saleTokenMintAmount = "2000";
|
|
const contributedTokensMintAmount = "20000";
|
|
const extraContributedTokensToMint = "5000";
|
|
|
|
// token to sell in ICCO
|
|
SOLD_TOKEN = await TokenImplementation.new();
|
|
SOLD_TOKEN_DECIMALS = tokenDecimals;
|
|
SOLD_TOKEN_BYTES32_ADDRESS =
|
|
"0x000000000000000000000000" + SOLD_TOKEN.address.substr(2);
|
|
const soldTokenName = "Sold Token";
|
|
const soldTokenSymbol = "SOLD";
|
|
|
|
await SOLD_TOKEN.initialize(
|
|
soldTokenName,
|
|
soldTokenSymbol,
|
|
SOLD_TOKEN_DECIMALS,
|
|
tokenSequence,
|
|
mintAccount,
|
|
tokenChainId,
|
|
nativeContractAddress
|
|
);
|
|
await SOLD_TOKEN.mint(SELLER, saleTokenMintAmount);
|
|
|
|
// first token to contribute in sale
|
|
CONTRIBUTED_TOKEN_ONE = await TokenImplementation.new();
|
|
const tokenOneName = "Contributed Stablecoin";
|
|
const tokenOneSymbol = "STABLE";
|
|
|
|
await CONTRIBUTED_TOKEN_ONE.initialize(
|
|
tokenOneName,
|
|
tokenOneSymbol,
|
|
SOLD_TOKEN_DECIMALS,
|
|
tokenSequence,
|
|
mintAccount,
|
|
tokenChainId,
|
|
nativeContractAddress
|
|
);
|
|
await CONTRIBUTED_TOKEN_ONE.mint(BUYER_ONE, contributedTokensMintAmount);
|
|
|
|
// second token to contribute to sale
|
|
CONTRIBUTED_TOKEN_TWO = await TokenImplementation.new();
|
|
const tokenTwoName = "Contributed Coin";
|
|
const tokenTwoSymbol = "COIN";
|
|
|
|
await CONTRIBUTED_TOKEN_TWO.initialize(
|
|
tokenTwoName,
|
|
tokenTwoSymbol,
|
|
SOLD_TOKEN_DECIMALS,
|
|
tokenSequence,
|
|
mintAccount,
|
|
tokenChainId,
|
|
nativeContractAddress
|
|
);
|
|
await CONTRIBUTED_TOKEN_TWO.mint(BUYER_TWO, contributedTokensMintAmount);
|
|
|
|
// mint some token two to buyer1 for multi-asset contribution test
|
|
await CONTRIBUTED_TOKEN_TWO.mint(BUYER_ONE, extraContributedTokensToMint);
|
|
});
|
|
|
|
// more global sale test variables
|
|
let SALE_START;
|
|
let SALE_END;
|
|
let SALE_INIT_PAYLOAD;
|
|
let SALE_ID = 0;
|
|
let TOKEN_ONE_INDEX = 0;
|
|
let TOKEN_TWO_INDEX = 1;
|
|
|
|
it("create a sale correctly and attest over wormhole", async function() {
|
|
console.log(
|
|
"\n -------------------------- Sale Test #1 (Successful) --------------------------"
|
|
);
|
|
|
|
// test variables
|
|
const current_block = await web3.eth.getBlock("latest");
|
|
SALE_START = current_block.timestamp + 5;
|
|
SALE_END = SALE_START + 8;
|
|
|
|
const saleTokenAmount = "1000";
|
|
const minimumTokenRaise = "2000";
|
|
const maximumTokenRaise = "30000";
|
|
const tokenOneConversionRate = "1000000000000000000";
|
|
const tokenTwoConversionRate = "2000000000000000000";
|
|
const saleRecipient = accounts[0];
|
|
const refundRecipient = accounts[0];
|
|
const acceptedTokenLength = 2;
|
|
const payloadIdType1 = "01";
|
|
|
|
const initialized = new web3.eth.Contract(
|
|
ConductorImplementationFullABI,
|
|
TokenSaleConductor.address
|
|
);
|
|
|
|
await SOLD_TOKEN.approve(TokenSaleConductor.address, saleTokenAmount);
|
|
|
|
// create array (struct) for sale params
|
|
const saleParams = [
|
|
SOLD_TOKEN_BYTES32_ADDRESS,
|
|
TEST_CHAIN_ID,
|
|
saleTokenAmount,
|
|
minimumTokenRaise,
|
|
maximumTokenRaise,
|
|
SALE_START,
|
|
SALE_END,
|
|
saleRecipient,
|
|
refundRecipient,
|
|
];
|
|
|
|
// create accepted tokens array
|
|
const acceptedTokens = [
|
|
[
|
|
TEST_CHAIN_ID,
|
|
"0x000000000000000000000000" + CONTRIBUTED_TOKEN_ONE.address.substr(2),
|
|
tokenOneConversionRate,
|
|
],
|
|
[
|
|
TEST_CHAIN_ID,
|
|
"0x000000000000000000000000" + CONTRIBUTED_TOKEN_TWO.address.substr(2),
|
|
tokenTwoConversionRate,
|
|
],
|
|
];
|
|
|
|
// create the sale
|
|
await initialized.methods.createSale(saleParams, acceptedTokens).send({
|
|
value: "0",
|
|
from: SELLER,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
// Verify Payload sent to contributor
|
|
const log = (
|
|
await WORMHOLE.getPastEvents("LogMessagePublished", {
|
|
fromBlock: "latest",
|
|
})
|
|
)[0].returnValues;
|
|
|
|
// verify payload
|
|
assert.equal(log.sender, TokenSaleConductor.address);
|
|
|
|
// payload id
|
|
let index = 2;
|
|
assert.equal(log.payload.substr(index, 2), payloadIdType1);
|
|
index += 2;
|
|
|
|
// sale id
|
|
assert.equal(parseInt(log.payload.substr(index, 64), 16), SALE_ID);
|
|
index += 64;
|
|
|
|
// token address
|
|
assert.equal(
|
|
log.payload.substr(index, 64),
|
|
web3.eth.abi.encodeParameter("address", SOLD_TOKEN.address).substring(2)
|
|
);
|
|
index += 64;
|
|
|
|
// token chain
|
|
assert.equal(
|
|
log.payload.substr(index, 4),
|
|
web3.eth.abi
|
|
.encodeParameter("uint16", TEST_CHAIN_ID)
|
|
.substring(2 + 64 - 4)
|
|
);
|
|
index += 4;
|
|
|
|
// token decimals
|
|
assert.equal(
|
|
parseInt(log.payload.substr(index, 2), 16),
|
|
SOLD_TOKEN_DECIMALS
|
|
);
|
|
index += 2;
|
|
|
|
// token amount
|
|
assert.equal(
|
|
parseInt(log.payload.substr(index, 64), 16),
|
|
parseInt(saleTokenAmount)
|
|
);
|
|
index += 64;
|
|
|
|
// min raise amount
|
|
assert.equal(
|
|
parseInt(log.payload.substr(index, 64), 16),
|
|
parseInt(minimumTokenRaise)
|
|
);
|
|
index += 64;
|
|
|
|
// max raise amount
|
|
assert.equal(
|
|
parseInt(log.payload.substr(index, 64), 16),
|
|
parseInt(maximumTokenRaise)
|
|
);
|
|
index += 64;
|
|
|
|
// timestamp start
|
|
assert.equal(parseInt(log.payload.substr(index, 64), 16), SALE_START);
|
|
index += 64;
|
|
|
|
// timestamp end
|
|
assert.equal(parseInt(log.payload.substr(index, 64), 16), SALE_END);
|
|
index += 64;
|
|
|
|
// accepted tokens length
|
|
assert.equal(
|
|
parseInt(log.payload.substr(index, 2), 16),
|
|
acceptedTokenLength
|
|
);
|
|
index += 2;
|
|
|
|
// token address
|
|
assert.equal(
|
|
log.payload.substr(index, 64),
|
|
web3.eth.abi
|
|
.encodeParameter("address", CONTRIBUTED_TOKEN_ONE.address)
|
|
.substring(2)
|
|
);
|
|
index += 64;
|
|
|
|
// token chain
|
|
assert.equal(
|
|
log.payload.substr(index, 4),
|
|
web3.eth.abi
|
|
.encodeParameter("uint16", TEST_CHAIN_ID)
|
|
.substring(2 + 64 - 4)
|
|
);
|
|
index += 4;
|
|
|
|
// conversion rate
|
|
assert.equal(
|
|
parseInt(log.payload.substr(index, 32), 16),
|
|
parseInt(tokenOneConversionRate)
|
|
);
|
|
index += 32;
|
|
|
|
// token address
|
|
assert.equal(
|
|
log.payload.substr(index, 64),
|
|
web3.eth.abi
|
|
.encodeParameter("address", CONTRIBUTED_TOKEN_TWO.address)
|
|
.substring(2)
|
|
);
|
|
index += 64;
|
|
|
|
// token chain
|
|
assert.equal(
|
|
log.payload.substr(index, 4),
|
|
web3.eth.abi
|
|
.encodeParameter("uint16", TEST_CHAIN_ID)
|
|
.substring(2 + 64 - 4)
|
|
);
|
|
index += 4;
|
|
|
|
// conversion rate
|
|
assert.equal(
|
|
parseInt(log.payload.substr(index, 32), 16),
|
|
parseInt(tokenTwoConversionRate)
|
|
);
|
|
index += 32;
|
|
|
|
// recipient of proceeds
|
|
assert.equal(
|
|
log.payload.substr(index, 64),
|
|
web3.eth.abi.encodeParameter("address", saleRecipient).substring(2)
|
|
);
|
|
index += 64;
|
|
|
|
// refund recipient in case the sale is aborted
|
|
assert.equal(
|
|
log.payload.substr(index, 64),
|
|
web3.eth.abi.encodeParameter("address", refundRecipient).substring(2)
|
|
);
|
|
index += 64;
|
|
|
|
assert.equal(log.payload.length, index);
|
|
SALE_INIT_PAYLOAD = log.payload.toString();
|
|
|
|
// verify that getNextSaleId is correct
|
|
const nextSaleId = await initialized.methods.getNextSaleId().call();
|
|
|
|
assert.equal(nextSaleId, SALE_ID + 1);
|
|
|
|
// confirm that the localTokenAddress was saved correctly
|
|
const sale = await initialized.methods.sales(SALE_ID).call();
|
|
|
|
assert.equal(SOLD_TOKEN.address, sale.localTokenAddress);
|
|
});
|
|
|
|
let INIT_SALE_VM;
|
|
|
|
it("should init a sale in the contributor", async function() {
|
|
// test variables
|
|
const saleTokenAmount = "1000";
|
|
const minimumTokenRaise = "2000";
|
|
const maximumTokenRaise = "30000";
|
|
const tokenOneConversionRate = "1000000000000000000";
|
|
const tokenTwoConversionRate = "2000000000000000000";
|
|
const saleRecipient = accounts[0];
|
|
const refundRecipient = accounts[0];
|
|
|
|
const initialized = new web3.eth.Contract(
|
|
ContributorImplementationFullABI,
|
|
TokenSaleContributor.address
|
|
);
|
|
|
|
// initialize the sale
|
|
const vm = await signAndEncodeVM(
|
|
1,
|
|
1,
|
|
TEST_CHAIN_ID,
|
|
"0x000000000000000000000000" + TokenSaleConductor.address.substr(2),
|
|
0,
|
|
SALE_INIT_PAYLOAD,
|
|
[testSigner1PK],
|
|
0,
|
|
0
|
|
);
|
|
|
|
await initialized.methods.initSale("0x" + vm).send({
|
|
from: SELLER,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
INIT_SALE_VM = vm;
|
|
|
|
// verify sale getter
|
|
const sale = await initialized.methods.sales(SALE_ID).call();
|
|
|
|
assert.equal(sale.saleID, SALE_ID);
|
|
assert.equal(
|
|
sale.tokenAddress.substring(2),
|
|
web3.eth.abi.encodeParameter("address", SOLD_TOKEN.address).substring(2)
|
|
);
|
|
assert.equal(sale.tokenChain, TEST_CHAIN_ID);
|
|
assert.equal(sale.tokenAmount, parseInt(saleTokenAmount));
|
|
assert.equal(sale.minRaise, parseInt(minimumTokenRaise));
|
|
assert.equal(sale.maxRaise, parseInt(maximumTokenRaise));
|
|
assert.equal(sale.saleStart, SALE_START);
|
|
assert.equal(sale.saleEnd, SALE_END);
|
|
assert.equal(
|
|
sale.acceptedTokensAddresses[TOKEN_ONE_INDEX].substring(2),
|
|
web3.eth.abi
|
|
.encodeParameter("address", CONTRIBUTED_TOKEN_ONE.address)
|
|
.substring(2)
|
|
);
|
|
assert.equal(sale.acceptedTokensChains[TOKEN_ONE_INDEX], TEST_CHAIN_ID);
|
|
assert.equal(
|
|
sale.acceptedTokensConversionRates[TOKEN_ONE_INDEX],
|
|
parseInt(tokenOneConversionRate)
|
|
);
|
|
assert.equal(
|
|
sale.acceptedTokensAddresses[TOKEN_TWO_INDEX].substring(2),
|
|
web3.eth.abi
|
|
.encodeParameter("address", CONTRIBUTED_TOKEN_TWO.address)
|
|
.substring(2)
|
|
);
|
|
assert.equal(sale.acceptedTokensChains[TOKEN_TWO_INDEX], TEST_CHAIN_ID);
|
|
assert.equal(
|
|
sale.acceptedTokensConversionRates[TOKEN_TWO_INDEX],
|
|
parseInt(tokenTwoConversionRate)
|
|
);
|
|
assert.equal(
|
|
sale.recipient.substring(2),
|
|
web3.eth.abi.encodeParameter("address", saleRecipient).substring(2)
|
|
);
|
|
assert.equal(
|
|
sale.refundRecipient.substring(2),
|
|
web3.eth.abi.encodeParameter("address", refundRecipient).substring(2)
|
|
);
|
|
assert.equal(sale.allocations[TOKEN_ONE_INDEX], 0);
|
|
assert.equal(sale.allocations[TOKEN_TWO_INDEX], 0);
|
|
assert.equal(sale.excessContributions[TOKEN_ONE_INDEX], 0);
|
|
assert.equal(sale.excessContributions[TOKEN_TWO_INDEX], 0);
|
|
assert.ok(!sale.isSealed);
|
|
assert.ok(!sale.isAborted);
|
|
|
|
// verify getsaleAcceptedTokenInfo getter
|
|
const tokenOneInfo = await initialized.methods
|
|
.getSaleAcceptedTokenInfo(SALE_ID, TOKEN_ONE_INDEX)
|
|
.call();
|
|
const tokenTwoInfo = await initialized.methods
|
|
.getSaleAcceptedTokenInfo(SALE_ID, TOKEN_TWO_INDEX)
|
|
.call();
|
|
|
|
assert.equal(
|
|
tokenOneInfo.tokenAddress.substring(2),
|
|
web3.eth.abi
|
|
.encodeParameter("address", CONTRIBUTED_TOKEN_ONE.address)
|
|
.substring(2)
|
|
);
|
|
assert.equal(tokenOneInfo.tokenChainId, TEST_CHAIN_ID);
|
|
assert.equal(tokenOneInfo.conversionRate, parseInt(tokenOneConversionRate));
|
|
assert.equal(
|
|
tokenTwoInfo.tokenAddress.substring(2),
|
|
web3.eth.abi
|
|
.encodeParameter("address", CONTRIBUTED_TOKEN_TWO.address)
|
|
.substring(2)
|
|
);
|
|
assert.equal(tokenTwoInfo.tokenChainId, TEST_CHAIN_ID);
|
|
assert.equal(tokenTwoInfo.conversionRate, parseInt(tokenTwoConversionRate));
|
|
|
|
// verify getSaleTimeFrame getter
|
|
const saleTimeframe = await initialized.methods
|
|
.getSaleTimeframe(SALE_ID)
|
|
.call();
|
|
|
|
assert.equal(saleTimeframe.start, SALE_START);
|
|
assert.equal(saleTimeframe.end, SALE_END);
|
|
|
|
// verify getSaleStatus getter
|
|
const saleStatus = await initialized.methods.getSaleStatus(SALE_ID).call();
|
|
|
|
assert.ok(!saleStatus.isSealed);
|
|
assert.ok(!saleStatus.isAborted);
|
|
});
|
|
|
|
it("sale should only be initialized once in the contributor", async function() {
|
|
const initialized = new web3.eth.Contract(
|
|
ContributorImplementationFullABI,
|
|
TokenSaleContributor.address
|
|
);
|
|
|
|
let failed = false;
|
|
try {
|
|
await initialized.methods.initSale("0x" + INIT_SALE_VM).send({
|
|
from: SELLER,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
} catch (e) {
|
|
assert.equal(
|
|
e.message,
|
|
"Returned error: VM Exception while processing transaction: revert sale already initiated"
|
|
);
|
|
failed = true;
|
|
}
|
|
|
|
assert.ok(failed);
|
|
});
|
|
|
|
it("should accept contributions in the contributor during the sale timeframe", async function() {
|
|
await wait(5);
|
|
|
|
// test variables
|
|
const tokenOneContributionAmount = ["5000", "5000"];
|
|
const tokenTwoContributionAmount = ["5000", "2500"];
|
|
|
|
const initialized = new web3.eth.Contract(
|
|
ContributorImplementationFullABI,
|
|
TokenSaleContributor.address
|
|
);
|
|
|
|
// approve contribution amounts
|
|
await CONTRIBUTED_TOKEN_ONE.approve(
|
|
TokenSaleContributor.address,
|
|
parseInt(tokenOneContributionAmount[0]) +
|
|
parseInt(tokenOneContributionAmount[1]),
|
|
{
|
|
from: BUYER_ONE,
|
|
}
|
|
);
|
|
await CONTRIBUTED_TOKEN_TWO.approve(
|
|
TokenSaleContributor.address,
|
|
tokenTwoContributionAmount[0],
|
|
{
|
|
from: BUYER_TWO,
|
|
}
|
|
);
|
|
await CONTRIBUTED_TOKEN_TWO.approve(
|
|
TokenSaleContributor.address,
|
|
tokenTwoContributionAmount[1],
|
|
{
|
|
from: BUYER_ONE,
|
|
}
|
|
);
|
|
|
|
// perform "kyc" and contribute to the token sale for BUYER_ONE
|
|
const kycSig1 = await signContribution(
|
|
CONDUCTOR_BYTES32_ADDRESS,
|
|
SALE_ID,
|
|
TOKEN_ONE_INDEX,
|
|
tokenOneContributionAmount[0],
|
|
BUYER_ONE,
|
|
kycSignerPK
|
|
);
|
|
await initialized.methods
|
|
.contribute(
|
|
SALE_ID,
|
|
TOKEN_ONE_INDEX,
|
|
parseInt(tokenOneContributionAmount[0]),
|
|
kycSig1
|
|
)
|
|
.send({
|
|
from: BUYER_ONE,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
const kycSig2 = await signContribution(
|
|
CONDUCTOR_BYTES32_ADDRESS,
|
|
SALE_ID,
|
|
TOKEN_ONE_INDEX,
|
|
tokenOneContributionAmount[1],
|
|
BUYER_ONE,
|
|
kycSignerPK
|
|
);
|
|
await initialized.methods
|
|
.contribute(
|
|
SALE_ID,
|
|
TOKEN_ONE_INDEX,
|
|
parseInt(tokenOneContributionAmount[1]),
|
|
kycSig2
|
|
)
|
|
.send({
|
|
from: BUYER_ONE,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
const kycSig3 = await signContribution(
|
|
CONDUCTOR_BYTES32_ADDRESS,
|
|
SALE_ID,
|
|
TOKEN_TWO_INDEX,
|
|
tokenTwoContributionAmount[1],
|
|
BUYER_ONE,
|
|
kycSignerPK
|
|
);
|
|
await initialized.methods
|
|
.contribute(
|
|
SALE_ID,
|
|
TOKEN_TWO_INDEX,
|
|
parseInt(tokenTwoContributionAmount[1]),
|
|
kycSig3
|
|
)
|
|
.send({
|
|
from: BUYER_ONE,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
// perform "kyc" and contribute tokens to the sale for BUYER_TWO
|
|
const kycSig4 = await signContribution(
|
|
CONDUCTOR_BYTES32_ADDRESS,
|
|
SALE_ID,
|
|
TOKEN_TWO_INDEX,
|
|
tokenTwoContributionAmount[0],
|
|
BUYER_TWO,
|
|
kycSignerPK
|
|
);
|
|
await initialized.methods
|
|
.contribute(
|
|
SALE_ID,
|
|
TOKEN_TWO_INDEX,
|
|
parseInt(tokenTwoContributionAmount[0]),
|
|
kycSig4
|
|
)
|
|
.send({
|
|
from: BUYER_TWO,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
// verify getSaleTotalContribution after contributing
|
|
const totalContributionsTokenOne = await initialized.methods
|
|
.getSaleTotalContribution(SALE_ID, TOKEN_ONE_INDEX)
|
|
.call();
|
|
const totalContributionsTokenTwo = await initialized.methods
|
|
.getSaleTotalContribution(SALE_ID, TOKEN_TWO_INDEX)
|
|
.call();
|
|
|
|
assert.equal(
|
|
totalContributionsTokenOne,
|
|
parseInt(tokenOneContributionAmount[0]) +
|
|
parseInt(tokenOneContributionAmount[1])
|
|
);
|
|
assert.equal(
|
|
totalContributionsTokenTwo,
|
|
parseInt(tokenTwoContributionAmount[0]) +
|
|
parseInt(tokenTwoContributionAmount[1])
|
|
);
|
|
|
|
// verify getSaleContribution
|
|
const buyerOneContributionTokenOne = await initialized.methods
|
|
.getSaleContribution(SALE_ID, TOKEN_ONE_INDEX, BUYER_ONE)
|
|
.call();
|
|
const buyerOneContributionTokenTwo = await initialized.methods
|
|
.getSaleContribution(SALE_ID, TOKEN_TWO_INDEX, BUYER_ONE)
|
|
.call();
|
|
const buyerTwoContribution = await initialized.methods
|
|
.getSaleContribution(SALE_ID, TOKEN_TWO_INDEX, BUYER_TWO)
|
|
.call();
|
|
|
|
assert.equal(
|
|
buyerOneContributionTokenOne,
|
|
parseInt(tokenOneContributionAmount[0]) +
|
|
parseInt(tokenOneContributionAmount[1])
|
|
);
|
|
assert.equal(
|
|
buyerOneContributionTokenTwo,
|
|
parseInt(tokenTwoContributionAmount[1])
|
|
);
|
|
assert.equal(buyerTwoContribution, parseInt(tokenTwoContributionAmount[0]));
|
|
});
|
|
|
|
it("should not accept contributions without proper KYC signature", async function() {
|
|
// test variables
|
|
const tokenOneContributionAmount = "10000";
|
|
|
|
const initialized = new web3.eth.Contract(
|
|
ContributorImplementationFullABI,
|
|
TokenSaleContributor.address
|
|
);
|
|
|
|
await CONTRIBUTED_TOKEN_ONE.approve(
|
|
TokenSaleContributor.address,
|
|
tokenOneContributionAmount,
|
|
{
|
|
from: BUYER_ONE,
|
|
}
|
|
);
|
|
|
|
let failed = false;
|
|
try {
|
|
// perform "kyc" and contribute to the token sale
|
|
const kycSig1 = await signContribution(
|
|
CONDUCTOR_BYTES32_ADDRESS,
|
|
SALE_ID,
|
|
TOKEN_ONE_INDEX,
|
|
tokenOneContributionAmount,
|
|
BUYER_ONE,
|
|
accounts[0]
|
|
);
|
|
await initialized.methods
|
|
.contribute(
|
|
SALE_ID,
|
|
TOKEN_ONE_INDEX,
|
|
parseInt(tokenOneContributionAmount),
|
|
kycSig1
|
|
)
|
|
.send({
|
|
from: BUYER_ONE,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
} catch (e) {
|
|
assert.equal(
|
|
e.message,
|
|
"Returned error: VM Exception while processing transaction: revert unauthorized contributor"
|
|
);
|
|
failed = true;
|
|
}
|
|
|
|
assert.ok(failed);
|
|
});
|
|
|
|
it("should not accept contributions in the contributor for non-existent saleIDs", async function() {
|
|
// test variables
|
|
const tokenOneContributionAmount = "10000";
|
|
const incorrect_sale_id = "42069";
|
|
|
|
const initialized = new web3.eth.Contract(
|
|
ContributorImplementationFullABI,
|
|
TokenSaleContributor.address
|
|
);
|
|
|
|
let failed = false;
|
|
try {
|
|
const kycSig1 = await signContribution(
|
|
CONDUCTOR_BYTES32_ADDRESS,
|
|
SALE_ID,
|
|
TOKEN_ONE_INDEX,
|
|
tokenOneContributionAmount,
|
|
BUYER_TWO,
|
|
kycSignerPK
|
|
);
|
|
await initialized.methods
|
|
.contribute(
|
|
incorrect_sale_id,
|
|
TOKEN_ONE_INDEX,
|
|
tokenOneContributionAmount,
|
|
kycSig1
|
|
)
|
|
.send({
|
|
from: BUYER_TWO,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
} catch (e) {
|
|
assert.equal(
|
|
e.message,
|
|
"Returned error: VM Exception while processing transaction: revert sale not initiated"
|
|
);
|
|
failed = true;
|
|
}
|
|
|
|
assert.ok(failed);
|
|
});
|
|
|
|
it("should not accept contributions after the sale has ended", async function() {
|
|
await wait(10);
|
|
|
|
// test variables
|
|
const tokenTwoContributionAmount = 5000;
|
|
|
|
const initialized = new web3.eth.Contract(
|
|
ContributorImplementationFullABI,
|
|
TokenSaleContributor.address
|
|
);
|
|
|
|
let failed = false;
|
|
try {
|
|
const kycSig1 = await signContribution(
|
|
CONDUCTOR_BYTES32_ADDRESS,
|
|
SALE_ID,
|
|
TOKEN_TWO_INDEX,
|
|
tokenTwoContributionAmount,
|
|
BUYER_TWO,
|
|
kycSignerPK
|
|
);
|
|
await initialized.methods
|
|
.contribute(
|
|
SALE_ID,
|
|
TOKEN_TWO_INDEX,
|
|
tokenTwoContributionAmount,
|
|
kycSig1
|
|
)
|
|
.send({
|
|
from: BUYER_TWO,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
} catch (e) {
|
|
assert.equal(
|
|
e.message,
|
|
"Returned error: VM Exception while processing transaction: revert sale has ended"
|
|
);
|
|
failed = true;
|
|
}
|
|
|
|
assert.ok(failed);
|
|
});
|
|
|
|
let CONTRIBUTIONS_PAYLOAD;
|
|
|
|
it("should attest contributions correctly", async function() {
|
|
// test variables
|
|
const tokenOneContributionAmount = 10000;
|
|
const tokenTwoContributionAmount = 7500;
|
|
const acceptedTokenLength = 2;
|
|
const payloadIdType2 = "02";
|
|
|
|
const initialized = new web3.eth.Contract(
|
|
ContributorImplementationFullABI,
|
|
TokenSaleContributor.address
|
|
);
|
|
|
|
// attest contributions
|
|
await initialized.methods.attestContributions(SALE_ID).send({
|
|
from: BUYER_ONE,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
const log = (
|
|
await WORMHOLE.getPastEvents("LogMessagePublished", {
|
|
fromBlock: "latest",
|
|
})
|
|
)[0].returnValues;
|
|
|
|
assert.equal(log.sender, TokenSaleContributor.address);
|
|
|
|
// payload id
|
|
let index = 2;
|
|
assert.equal(log.payload.substr(index, 2), payloadIdType2);
|
|
index += 2;
|
|
|
|
// sale id
|
|
assert.equal(parseInt(log.payload.substr(index, 64), 16), SALE_ID);
|
|
index += 64;
|
|
|
|
// chain id
|
|
assert.equal(
|
|
log.payload.substr(index, 4),
|
|
web3.eth.abi.encodeParameter("uint16", 2).substring(2 + 64 - 4)
|
|
);
|
|
index += 4;
|
|
|
|
// tokens length
|
|
assert.equal(
|
|
parseInt(log.payload.substr(index, 2), 16),
|
|
acceptedTokenLength
|
|
);
|
|
index += 2;
|
|
|
|
// token index
|
|
assert.equal(
|
|
log.payload.substr(index, 2),
|
|
web3.eth.abi
|
|
.encodeParameter("uint8", TOKEN_ONE_INDEX)
|
|
.substring(2 + 64 - 2)
|
|
);
|
|
index += 2;
|
|
|
|
// amount
|
|
assert.equal(
|
|
parseInt(log.payload.substr(index, 64), 16),
|
|
tokenOneContributionAmount
|
|
);
|
|
index += 64;
|
|
|
|
// token index
|
|
assert.equal(
|
|
log.payload.substr(index, 2),
|
|
web3.eth.abi
|
|
.encodeParameter("uint8", TOKEN_TWO_INDEX)
|
|
.substring(2 + 64 - 2)
|
|
);
|
|
index += 2;
|
|
|
|
// amount
|
|
assert.equal(
|
|
parseInt(log.payload.substr(index, 64), 16),
|
|
tokenTwoContributionAmount
|
|
);
|
|
index += 64;
|
|
|
|
assert.equal(log.payload.length, index);
|
|
CONTRIBUTIONS_PAYLOAD = log.payload.toString();
|
|
});
|
|
|
|
it("conductor should collect contributions correctly", async function() {
|
|
// test variables
|
|
const tokenOneContributionAmount = 10000;
|
|
const tokenTwoContributionAmount = 7500;
|
|
|
|
const initialized = new web3.eth.Contract(
|
|
ConductorImplementationFullABI,
|
|
TokenSaleConductor.address
|
|
);
|
|
|
|
// verify saleContributionIsCollected getter before collecting contributions
|
|
const isContributionOneCollectedBefore = await initialized.methods
|
|
.saleContributionIsCollected(SALE_ID, TOKEN_ONE_INDEX)
|
|
.call();
|
|
const isContributionTwoCollectedBefore = await initialized.methods
|
|
.saleContributionIsCollected(SALE_ID, TOKEN_TWO_INDEX)
|
|
.call();
|
|
|
|
assert.ok(!isContributionOneCollectedBefore);
|
|
assert.ok(!isContributionTwoCollectedBefore);
|
|
|
|
const vm = await signAndEncodeVM(
|
|
1,
|
|
1,
|
|
TEST_CHAIN_ID,
|
|
"0x000000000000000000000000" + TokenSaleContributor.address.substr(2),
|
|
0,
|
|
CONTRIBUTIONS_PAYLOAD,
|
|
[testSigner1PK],
|
|
0,
|
|
0
|
|
);
|
|
|
|
await initialized.methods.collectContribution("0x" + vm).send({
|
|
from: SELLER,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
// verify saleContributionIsCollected getter after collecting contributions
|
|
const isContributionOneCollectedAfter = await initialized.methods
|
|
.saleContributionIsCollected(SALE_ID, TOKEN_ONE_INDEX)
|
|
.call();
|
|
const isContributionTwoCollectedAfter = await initialized.methods
|
|
.saleContributionIsCollected(SALE_ID, TOKEN_TWO_INDEX)
|
|
.call();
|
|
|
|
assert.ok(isContributionOneCollectedAfter);
|
|
assert.ok(isContributionTwoCollectedAfter);
|
|
|
|
// verify saleContributions getter
|
|
const contributions = await initialized.methods
|
|
.saleContributions(SALE_ID)
|
|
.call();
|
|
|
|
assert.equal(contributions[0], tokenOneContributionAmount);
|
|
assert.equal(contributions[1], tokenTwoContributionAmount);
|
|
});
|
|
|
|
it("conductor should not collect contributions more than once", async function() {
|
|
const initialized = new web3.eth.Contract(
|
|
ConductorImplementationFullABI,
|
|
TokenSaleConductor.address
|
|
);
|
|
|
|
const vm = await signAndEncodeVM(
|
|
1,
|
|
1,
|
|
TEST_CHAIN_ID,
|
|
"0x000000000000000000000000" + TokenSaleContributor.address.substr(2),
|
|
0,
|
|
CONTRIBUTIONS_PAYLOAD,
|
|
[testSigner1PK],
|
|
0,
|
|
0
|
|
);
|
|
|
|
let failed = false;
|
|
try {
|
|
// try to collect contributions again
|
|
await initialized.methods.collectContribution("0x" + vm).send({
|
|
from: SELLER,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
} catch (e) {
|
|
assert.equal(
|
|
e.message,
|
|
"Returned error: VM Exception while processing transaction: revert already collected contribution"
|
|
);
|
|
failed = true;
|
|
}
|
|
|
|
assert.ok(failed);
|
|
});
|
|
|
|
let SALE_SEALED_PAYLOAD;
|
|
|
|
it("conductor should not seal a sale for a non-existent saleID", async function() {
|
|
const initialized = new web3.eth.Contract(
|
|
ConductorImplementationFullABI,
|
|
TokenSaleConductor.address
|
|
);
|
|
|
|
let failed = false;
|
|
try {
|
|
// try to seal a non-existent sale
|
|
await initialized.methods.sealSale(SALE_ID + 10).send({
|
|
from: SELLER,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
} catch (e) {
|
|
assert.equal(
|
|
e.message,
|
|
"Returned error: VM Exception while processing transaction: revert sale not initiated"
|
|
);
|
|
failed = true;
|
|
}
|
|
|
|
assert.ok(failed);
|
|
});
|
|
|
|
it("conductor should seal the sale correctly and distribute tokens", async function() {
|
|
// test variables
|
|
const expectedContributorBalanceBefore = "0";
|
|
const expectedConductorBalanceBefore = "1000";
|
|
const expectedContributorBalanceAfter = "1000";
|
|
const expectedConductorBalanceAfter = "0";
|
|
const payloadIdType3 = "03";
|
|
|
|
const initialized = new web3.eth.Contract(
|
|
ConductorImplementationFullABI,
|
|
TokenSaleConductor.address
|
|
);
|
|
|
|
const actualContributorBalanceBefore = await SOLD_TOKEN.balanceOf(
|
|
TokenSaleContributor.address
|
|
);
|
|
const actualConductorBalanceBefore = await SOLD_TOKEN.balanceOf(
|
|
TokenSaleConductor.address
|
|
);
|
|
|
|
assert.equal(
|
|
actualContributorBalanceBefore,
|
|
expectedContributorBalanceBefore
|
|
);
|
|
assert.equal(actualConductorBalanceBefore, expectedConductorBalanceBefore);
|
|
|
|
// verify sealSealed flag in sales
|
|
const saleBefore = await initialized.methods.sales(SALE_ID).call();
|
|
|
|
assert.ok(!saleBefore.isSealed);
|
|
|
|
// seal the sale
|
|
await initialized.methods.sealSale(SALE_ID).send({
|
|
from: SELLER,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
const actualContributorBalanceAfter = await SOLD_TOKEN.balanceOf(
|
|
TokenSaleContributor.address
|
|
);
|
|
const actualConductorBalanceAfter = await SOLD_TOKEN.balanceOf(
|
|
TokenSaleConductor.address
|
|
);
|
|
|
|
assert.equal(
|
|
actualContributorBalanceAfter,
|
|
expectedContributorBalanceAfter
|
|
);
|
|
assert.equal(actualConductorBalanceAfter, expectedConductorBalanceAfter);
|
|
|
|
const log = (
|
|
await WORMHOLE.getPastEvents("LogMessagePublished", {
|
|
fromBlock: "latest",
|
|
})
|
|
)[0].returnValues;
|
|
|
|
// verify saleSealed payload
|
|
assert.equal(log.sender, TokenSaleConductor.address);
|
|
|
|
// payload id
|
|
let index = 2;
|
|
assert.equal(log.payload.substr(index, 2), payloadIdType3);
|
|
index += 2;
|
|
|
|
// sale id
|
|
assert.equal(parseInt(log.payload.substr(index, 64), 16), SALE_ID);
|
|
index += 64;
|
|
|
|
SALE_SEALED_PAYLOAD = log.payload;
|
|
|
|
// verify saleSealed flag after sealing the sale
|
|
const saleAfter = await initialized.methods.sales(SALE_ID).call();
|
|
|
|
assert.ok(saleAfter.isSealed);
|
|
});
|
|
|
|
it("contributor should seal a sale correctly", async function() {
|
|
// test variables
|
|
const expectedAllocationTokenOne = "400";
|
|
const expectedAllocationTokenTwo = "600";
|
|
const expectedExcessTokenOne = "0";
|
|
const expectedExcessTokenTwo = "0";
|
|
const expectedRecipientTokenOneBalanceChange = "10000";
|
|
const expectedRecipientTokenTwoBalanceChange = "7500";
|
|
|
|
const initialized = new web3.eth.Contract(
|
|
ContributorImplementationFullABI,
|
|
TokenSaleContributor.address
|
|
);
|
|
|
|
// grab contributed token balance before for sale recipient
|
|
const receipientTokenOneBalanceBefore = await CONTRIBUTED_TOKEN_ONE.balanceOf(
|
|
SELLER
|
|
);
|
|
const receipientTokenTwoBalanceBefore = await CONTRIBUTED_TOKEN_TWO.balanceOf(
|
|
SELLER
|
|
);
|
|
|
|
// verify sealSealed getters before calling saleSealed
|
|
const saleBefore = await initialized.methods.sales(SALE_ID).call();
|
|
|
|
// verify isSealed flag before
|
|
assert.ok(!saleBefore.isSealed);
|
|
|
|
const vm = await signAndEncodeVM(
|
|
1,
|
|
1,
|
|
TEST_CHAIN_ID,
|
|
"0x000000000000000000000000" + TokenSaleConductor.address.substr(2),
|
|
0,
|
|
SALE_SEALED_PAYLOAD,
|
|
[testSigner1PK],
|
|
0,
|
|
0
|
|
);
|
|
|
|
// seal the sale
|
|
await initialized.methods.saleSealed("0x" + vm).send({
|
|
from: BUYER_ONE,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
// confirm that the sale was sealed
|
|
const saleAfter = await initialized.methods.sales(SALE_ID).call();
|
|
|
|
assert.ok(saleAfter.isSealed);
|
|
|
|
// verify getSaleAllocation after sealing the sale
|
|
const actualAllocationTokenOne = await initialized.methods
|
|
.getSaleAllocation(SALE_ID, TOKEN_ONE_INDEX)
|
|
.call();
|
|
const actualAllocationTokenTwo = await initialized.methods
|
|
.getSaleAllocation(SALE_ID, TOKEN_TWO_INDEX)
|
|
.call();
|
|
|
|
assert.equal(actualAllocationTokenOne, expectedAllocationTokenOne);
|
|
assert.equal(actualAllocationTokenTwo, expectedAllocationTokenTwo);
|
|
|
|
// verify getSaleAllocation after sealing the sale
|
|
const actualExcessTokenOne = await initialized.methods
|
|
.getSaleExcessContribution(SALE_ID, TOKEN_ONE_INDEX)
|
|
.call();
|
|
const actualExcessTokenTwo = await initialized.methods
|
|
.getSaleExcessContribution(SALE_ID, TOKEN_TWO_INDEX)
|
|
.call();
|
|
|
|
assert.equal(actualExcessTokenOne, expectedExcessTokenOne);
|
|
assert.equal(actualExcessTokenTwo, expectedExcessTokenTwo);
|
|
|
|
// confirm that the sale recipient recieved the correct amount of contributions
|
|
const receipientTokenOneBalanceAfter = await CONTRIBUTED_TOKEN_ONE.balanceOf(
|
|
SELLER
|
|
);
|
|
const receipientTokenTwoBalanceAfter = await CONTRIBUTED_TOKEN_TWO.balanceOf(
|
|
SELLER
|
|
);
|
|
|
|
assert.equal(
|
|
receipientTokenOneBalanceAfter - receipientTokenOneBalanceBefore,
|
|
expectedRecipientTokenOneBalanceChange
|
|
);
|
|
assert.equal(
|
|
receipientTokenTwoBalanceAfter - receipientTokenTwoBalanceBefore,
|
|
expectedRecipientTokenTwoBalanceChange
|
|
);
|
|
});
|
|
|
|
let ONE_CLAIM_SNAPSHOT;
|
|
|
|
it("contributor should distribute tokens correctly", async function() {
|
|
// test variables
|
|
const expectedContributorBalanceBefore = "1000";
|
|
const expectedBuyerOneBalanceBefore = "0";
|
|
const expectedBuyerTwoBalanceBefore = "0";
|
|
const expectedContributorBalanceAfter = "0";
|
|
const expectedBuyerOneBalanceAfter = "600";
|
|
const expectedBuyerTwoBalanceAfter = "400";
|
|
|
|
const initialized = new web3.eth.Contract(
|
|
ContributorImplementationFullABI,
|
|
TokenSaleContributor.address
|
|
);
|
|
|
|
// check balances before claiming allocations
|
|
const actualContributorBalanceBefore = await SOLD_TOKEN.balanceOf(
|
|
TokenSaleContributor.address
|
|
);
|
|
const actualBuyerOneBalanceBefore = await SOLD_TOKEN.balanceOf(BUYER_ONE);
|
|
const actualBuyerTwoBalanceBefore = await SOLD_TOKEN.balanceOf(BUYER_TWO);
|
|
|
|
assert.equal(
|
|
actualContributorBalanceBefore,
|
|
expectedContributorBalanceBefore
|
|
);
|
|
assert.equal(actualBuyerOneBalanceBefore, expectedBuyerOneBalanceBefore);
|
|
assert.equal(actualBuyerTwoBalanceBefore, expectedBuyerTwoBalanceBefore);
|
|
|
|
// verify allocationIsClaimed before claiming allocation
|
|
const isAllocationClaimedBuyerOneTokenOneBefore = await initialized.methods
|
|
.allocationIsClaimed(SALE_ID, TOKEN_ONE_INDEX, BUYER_ONE)
|
|
.call();
|
|
const isAllocationClaimedBuyerOneTokenTwoBefore = await initialized.methods
|
|
.allocationIsClaimed(SALE_ID, TOKEN_TWO_INDEX, BUYER_ONE)
|
|
.call();
|
|
const isAllocationClaimedTokenTwoBefore = await initialized.methods
|
|
.allocationIsClaimed(SALE_ID, TOKEN_TWO_INDEX, BUYER_TWO)
|
|
.call();
|
|
|
|
assert.ok(!isAllocationClaimedBuyerOneTokenOneBefore);
|
|
assert.ok(!isAllocationClaimedBuyerOneTokenTwoBefore);
|
|
assert.ok(!isAllocationClaimedTokenTwoBefore);
|
|
|
|
// claim allocations for both tokens
|
|
await initialized.methods.claimAllocation(SALE_ID, TOKEN_TWO_INDEX).send({
|
|
from: BUYER_TWO,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
ONE_CLAIM_SNAPSHOT = await snapshot();
|
|
|
|
await initialized.methods.claimAllocation(SALE_ID, TOKEN_ONE_INDEX).send({
|
|
from: BUYER_ONE,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
await initialized.methods.claimAllocation(SALE_ID, TOKEN_TWO_INDEX).send({
|
|
from: BUYER_ONE,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
// check balances after claiming allocations
|
|
const actualContributorBalanceAfter = await SOLD_TOKEN.balanceOf(
|
|
TokenSaleContributor.address
|
|
);
|
|
const actualBuyerOneBalanceAfter = await SOLD_TOKEN.balanceOf(BUYER_ONE);
|
|
const actualBuyerTwoBalanceAfter = await SOLD_TOKEN.balanceOf(BUYER_TWO);
|
|
|
|
assert.equal(
|
|
actualContributorBalanceAfter,
|
|
expectedContributorBalanceAfter
|
|
);
|
|
assert.equal(actualBuyerOneBalanceAfter, expectedBuyerOneBalanceAfter);
|
|
assert.equal(actualBuyerTwoBalanceAfter, expectedBuyerTwoBalanceAfter);
|
|
|
|
// verify allocationIsClaimed after claiming allocation
|
|
const isAllocationClaimedBuyerOneTokenOneAfter = await initialized.methods
|
|
.allocationIsClaimed(SALE_ID, TOKEN_ONE_INDEX, BUYER_ONE)
|
|
.call();
|
|
const isAllocationClaimedBuyerOneTokenTwoAfter = await initialized.methods
|
|
.allocationIsClaimed(SALE_ID, TOKEN_TWO_INDEX, BUYER_ONE)
|
|
.call();
|
|
const isAllocationClaimedTokenTwoAfter = await initialized.methods
|
|
.allocationIsClaimed(SALE_ID, TOKEN_TWO_INDEX, BUYER_TWO)
|
|
.call();
|
|
|
|
assert.ok(isAllocationClaimedBuyerOneTokenOneAfter);
|
|
assert.ok(isAllocationClaimedBuyerOneTokenTwoAfter);
|
|
assert.ok(isAllocationClaimedTokenTwoAfter);
|
|
});
|
|
|
|
it("allocation should only be claimable once", async function() {
|
|
await revert(ONE_CLAIM_SNAPSHOT);
|
|
|
|
const initialized = new web3.eth.Contract(
|
|
ContributorImplementationFullABI,
|
|
TokenSaleContributor.address
|
|
);
|
|
|
|
let failed = false;
|
|
try {
|
|
await initialized.methods.claimAllocation(SALE_ID, TOKEN_TWO_INDEX).send({
|
|
from: BUYER_TWO,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
} catch (e) {
|
|
assert.equal(
|
|
e.message,
|
|
"Returned error: VM Exception while processing transaction: revert allocation already claimed"
|
|
);
|
|
failed = true;
|
|
}
|
|
|
|
assert.ok(failed);
|
|
});
|
|
|
|
let SALE_2_START;
|
|
let SALE_2_END;
|
|
let SALE_2_INIT_PAYLOAD;
|
|
let SALE_2_ID;
|
|
const SALE_2_REFUND_RECIPIENT = accounts[5];
|
|
|
|
it("create a second sale correctly and attest over wormhole", async function() {
|
|
console.log(
|
|
"\n -------------------------- Sale Test #2 (Undersubscribed & Aborted) --------------------------"
|
|
);
|
|
|
|
// test variables
|
|
const current_block = await web3.eth.getBlock("latest");
|
|
SALE_2_START = current_block.timestamp + 5;
|
|
SALE_2_END = SALE_2_START + 8;
|
|
const saleTokenAmount = "1000";
|
|
const minimumTokenRaise = "2000";
|
|
const maximumTokenRaise = "30000";
|
|
const tokenOneConversionRate = "1000000000000000000";
|
|
const tokenTwoConversionRate = "2000000000000000000";
|
|
const saleRecipient = accounts[0];
|
|
const refundRecipient = SALE_2_REFUND_RECIPIENT;
|
|
const acceptedTokenLength = 2;
|
|
const payloadIdType1 = "01";
|
|
|
|
const initialized = new web3.eth.Contract(
|
|
ConductorImplementationFullABI,
|
|
TokenSaleConductor.address
|
|
);
|
|
|
|
await SOLD_TOKEN.approve(TokenSaleConductor.address, saleTokenAmount);
|
|
|
|
// create array (struct) for sale params
|
|
const saleParams = [
|
|
SOLD_TOKEN_BYTES32_ADDRESS,
|
|
TEST_CHAIN_ID,
|
|
saleTokenAmount,
|
|
minimumTokenRaise,
|
|
maximumTokenRaise,
|
|
SALE_2_START,
|
|
SALE_2_END,
|
|
saleRecipient,
|
|
refundRecipient,
|
|
];
|
|
|
|
// create accepted tokens array
|
|
const acceptedTokens = [
|
|
[
|
|
TEST_CHAIN_ID,
|
|
"0x000000000000000000000000" + CONTRIBUTED_TOKEN_ONE.address.substr(2),
|
|
tokenOneConversionRate,
|
|
],
|
|
[
|
|
TEST_CHAIN_ID,
|
|
"0x000000000000000000000000" + CONTRIBUTED_TOKEN_TWO.address.substr(2),
|
|
tokenTwoConversionRate,
|
|
],
|
|
];
|
|
|
|
// create a second sale
|
|
await initialized.methods.createSale(saleParams, acceptedTokens).send({
|
|
value: "0",
|
|
from: SELLER,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
const log = (
|
|
await WORMHOLE.getPastEvents("LogMessagePublished", {
|
|
fromBlock: "latest",
|
|
})
|
|
)[0].returnValues;
|
|
|
|
assert.equal(log.sender, TokenSaleConductor.address);
|
|
|
|
// payload id
|
|
let index = 2;
|
|
assert.equal(log.payload.substr(index, 2), payloadIdType1);
|
|
index += 2;
|
|
|
|
// sale id, should == 1 since it's the second sale
|
|
SALE_2_ID = 1;
|
|
assert.equal(parseInt(log.payload.substr(index, 64), 16), SALE_2_ID);
|
|
index += 64;
|
|
|
|
// token address
|
|
assert.equal(
|
|
log.payload.substr(index, 64),
|
|
web3.eth.abi.encodeParameter("address", SOLD_TOKEN.address).substring(2)
|
|
);
|
|
index += 64;
|
|
|
|
// token chain
|
|
assert.equal(
|
|
log.payload.substr(index, 4),
|
|
web3.eth.abi
|
|
.encodeParameter("uint16", TEST_CHAIN_ID)
|
|
.substring(2 + 64 - 4)
|
|
);
|
|
index += 4;
|
|
|
|
// token decimals
|
|
assert.equal(
|
|
parseInt(log.payload.substr(index, 2), 16),
|
|
SOLD_TOKEN_DECIMALS
|
|
);
|
|
index += 2;
|
|
|
|
// token amount
|
|
assert.equal(
|
|
parseInt(log.payload.substr(index, 64), 16),
|
|
parseInt(saleTokenAmount)
|
|
);
|
|
index += 64;
|
|
|
|
// min raise amount
|
|
assert.equal(
|
|
parseInt(log.payload.substr(index, 64), 16),
|
|
parseInt(minimumTokenRaise)
|
|
);
|
|
index += 64;
|
|
|
|
// max raise amount
|
|
assert.equal(
|
|
parseInt(log.payload.substr(index, 64), 16),
|
|
parseInt(maximumTokenRaise)
|
|
);
|
|
index += 64;
|
|
|
|
// timestamp start
|
|
assert.equal(parseInt(log.payload.substr(index, 64), 16), SALE_2_START);
|
|
index += 64;
|
|
|
|
// timestamp end
|
|
assert.equal(parseInt(log.payload.substr(index, 64), 16), SALE_2_END);
|
|
index += 64;
|
|
|
|
// accepted tokens length
|
|
assert.equal(
|
|
parseInt(log.payload.substr(index, 2), 16),
|
|
acceptedTokenLength
|
|
);
|
|
index += 2;
|
|
|
|
// token address
|
|
assert.equal(
|
|
log.payload.substr(index, 64),
|
|
web3.eth.abi
|
|
.encodeParameter("address", CONTRIBUTED_TOKEN_ONE.address)
|
|
.substring(2)
|
|
);
|
|
index += 64;
|
|
|
|
// token chain
|
|
assert.equal(
|
|
log.payload.substr(index, 4),
|
|
web3.eth.abi
|
|
.encodeParameter("uint16", TEST_CHAIN_ID)
|
|
.substring(2 + 64 - 4)
|
|
);
|
|
index += 4;
|
|
|
|
// conversion rate
|
|
assert.equal(
|
|
parseInt(log.payload.substr(index, 32), 16),
|
|
parseInt(tokenOneConversionRate)
|
|
);
|
|
index += 32;
|
|
|
|
// token address
|
|
assert.equal(
|
|
log.payload.substr(index, 64),
|
|
web3.eth.abi
|
|
.encodeParameter("address", CONTRIBUTED_TOKEN_TWO.address)
|
|
.substring(2)
|
|
);
|
|
index += 64;
|
|
|
|
// token chain
|
|
assert.equal(
|
|
log.payload.substr(index, 4),
|
|
web3.eth.abi
|
|
.encodeParameter("uint16", TEST_CHAIN_ID)
|
|
.substring(2 + 64 - 4)
|
|
);
|
|
index += 4;
|
|
|
|
// conversion rate
|
|
assert.equal(
|
|
parseInt(log.payload.substr(index, 32), 16),
|
|
parseInt(tokenTwoConversionRate)
|
|
);
|
|
index += 32;
|
|
|
|
// recipient of proceeds
|
|
assert.equal(
|
|
log.payload.substr(index, 64),
|
|
web3.eth.abi.encodeParameter("address", saleRecipient).substring(2)
|
|
);
|
|
index += 64;
|
|
|
|
// refund recipient in case the sale is aborted
|
|
assert.equal(
|
|
log.payload.substr(index, 64),
|
|
web3.eth.abi.encodeParameter("address", refundRecipient).substring(2)
|
|
);
|
|
index += 64;
|
|
|
|
assert.equal(log.payload.length, index);
|
|
SALE_2_INIT_PAYLOAD = log.payload.toString();
|
|
|
|
// verify that getNextSaleId is correct
|
|
const nextSaleId = await initialized.methods.getNextSaleId().call();
|
|
|
|
assert.equal(nextSaleId, SALE_2_ID + 1);
|
|
|
|
// confirm that the localTokenAddress was saved correctl
|
|
const sale = await initialized.methods.sales(SALE_2_ID).call();
|
|
|
|
assert.equal(SOLD_TOKEN.address, sale.localTokenAddress);
|
|
});
|
|
|
|
it("should init a second sale in the contributor", async function() {
|
|
// test variables
|
|
const saleTokenAmount = 1000;
|
|
const minimumTokenRaise = 2000;
|
|
const maximumTokenRaise = 30000;
|
|
const tokenOneConversionRate = 1000000000000000000;
|
|
const tokenTwoConversionRate = 2000000000000000000;
|
|
const saleRecipient = accounts[0];
|
|
const refundRecipient = SALE_2_REFUND_RECIPIENT;
|
|
|
|
const initialized = new web3.eth.Contract(
|
|
ContributorImplementationFullABI,
|
|
TokenSaleContributor.address
|
|
);
|
|
|
|
const vm = await signAndEncodeVM(
|
|
1,
|
|
1,
|
|
TEST_CHAIN_ID,
|
|
"0x000000000000000000000000" + TokenSaleConductor.address.substr(2),
|
|
0,
|
|
SALE_2_INIT_PAYLOAD,
|
|
[testSigner1PK],
|
|
0,
|
|
0
|
|
);
|
|
|
|
// initialize the second sale
|
|
await initialized.methods.initSale("0x" + vm).send({
|
|
from: SELLER,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
// verify sale getter
|
|
const sale = await initialized.methods.sales(SALE_2_ID).call();
|
|
|
|
assert.equal(sale.saleID, SALE_2_ID);
|
|
assert.equal(
|
|
sale.tokenAddress.substring(2),
|
|
web3.eth.abi.encodeParameter("address", SOLD_TOKEN.address).substring(2)
|
|
);
|
|
assert.equal(sale.tokenChain, TEST_CHAIN_ID);
|
|
assert.equal(sale.tokenAmount, saleTokenAmount);
|
|
assert.equal(sale.minRaise, minimumTokenRaise);
|
|
assert.equal(sale.maxRaise, parseInt(maximumTokenRaise));
|
|
assert.equal(sale.saleStart, SALE_2_START);
|
|
assert.equal(sale.saleEnd, SALE_2_END);
|
|
assert.equal(
|
|
sale.acceptedTokensAddresses[TOKEN_ONE_INDEX].substring(2),
|
|
web3.eth.abi
|
|
.encodeParameter("address", CONTRIBUTED_TOKEN_ONE.address)
|
|
.substring(2)
|
|
);
|
|
assert.equal(sale.acceptedTokensChains[TOKEN_ONE_INDEX], TEST_CHAIN_ID);
|
|
assert.equal(
|
|
sale.acceptedTokensConversionRates[TOKEN_ONE_INDEX],
|
|
tokenOneConversionRate
|
|
);
|
|
assert.equal(
|
|
sale.acceptedTokensAddresses[TOKEN_TWO_INDEX].substring(2),
|
|
web3.eth.abi
|
|
.encodeParameter("address", CONTRIBUTED_TOKEN_TWO.address)
|
|
.substring(2)
|
|
);
|
|
assert.equal(sale.acceptedTokensChains[TOKEN_TWO_INDEX], TEST_CHAIN_ID);
|
|
assert.equal(
|
|
sale.acceptedTokensConversionRates[TOKEN_TWO_INDEX],
|
|
tokenTwoConversionRate
|
|
);
|
|
assert.equal(
|
|
sale.recipient.substring(2),
|
|
web3.eth.abi.encodeParameter("address", saleRecipient).substring(2)
|
|
);
|
|
assert.equal(
|
|
sale.refundRecipient.substring(2),
|
|
web3.eth.abi.encodeParameter("address", refundRecipient).substring(2)
|
|
);
|
|
assert.equal(sale.allocations[TOKEN_ONE_INDEX], 0);
|
|
assert.equal(sale.allocations[TOKEN_TWO_INDEX], 0);
|
|
assert.equal(sale.excessContributions[TOKEN_ONE_INDEX], 0);
|
|
assert.equal(sale.excessContributions[TOKEN_TWO_INDEX], 0);
|
|
assert.ok(!sale.isSealed);
|
|
assert.ok(!sale.isAborted);
|
|
|
|
// verify getsaleAcceptedTokenInfo getter
|
|
const tokenOneInfo = await initialized.methods
|
|
.getSaleAcceptedTokenInfo(SALE_2_ID, TOKEN_ONE_INDEX)
|
|
.call();
|
|
const tokenTwoInfo = await initialized.methods
|
|
.getSaleAcceptedTokenInfo(SALE_2_ID, TOKEN_TWO_INDEX)
|
|
.call();
|
|
|
|
assert.equal(
|
|
tokenOneInfo.tokenAddress.substring(2),
|
|
web3.eth.abi
|
|
.encodeParameter("address", CONTRIBUTED_TOKEN_ONE.address)
|
|
.substring(2)
|
|
);
|
|
assert.equal(tokenOneInfo.tokenChainId, TEST_CHAIN_ID);
|
|
assert.equal(tokenOneInfo.conversionRate, tokenOneConversionRate);
|
|
assert.equal(
|
|
tokenTwoInfo.tokenAddress.substring(2),
|
|
web3.eth.abi
|
|
.encodeParameter("address", CONTRIBUTED_TOKEN_TWO.address)
|
|
.substring(2)
|
|
);
|
|
assert.equal(tokenTwoInfo.tokenChainId, TEST_CHAIN_ID);
|
|
assert.equal(tokenTwoInfo.conversionRate, tokenTwoConversionRate);
|
|
|
|
// verify getSaleTimeFrame getter
|
|
const saleTimeframe = await initialized.methods
|
|
.getSaleTimeframe(SALE_2_ID)
|
|
.call();
|
|
|
|
assert.equal(saleTimeframe.start, SALE_2_START);
|
|
assert.equal(saleTimeframe.end, SALE_2_END);
|
|
|
|
// verify getSaleStatus getter
|
|
const saleStatus = await initialized.methods
|
|
.getSaleStatus(SALE_2_ID)
|
|
.call();
|
|
|
|
assert.ok(!saleStatus.isSealed);
|
|
assert.ok(!saleStatus.isAborted);
|
|
});
|
|
|
|
it("should accept contributions in the contributor during the second sale timeframe", async function() {
|
|
await wait(5);
|
|
|
|
// test variables
|
|
const tokenOneContributionAmount = ["500", "500"];
|
|
const tokenTwoContributionAmount = ["100", "100"];
|
|
|
|
const initialized = new web3.eth.Contract(
|
|
ContributorImplementationFullABI,
|
|
TokenSaleContributor.address
|
|
);
|
|
|
|
// approve contribution amounts
|
|
await CONTRIBUTED_TOKEN_ONE.approve(
|
|
TokenSaleContributor.address,
|
|
parseInt(tokenOneContributionAmount[0]) +
|
|
parseInt(tokenOneContributionAmount[1]),
|
|
{
|
|
from: BUYER_ONE,
|
|
}
|
|
);
|
|
await CONTRIBUTED_TOKEN_TWO.approve(
|
|
TokenSaleContributor.address,
|
|
tokenTwoContributionAmount[0],
|
|
{
|
|
from: BUYER_TWO,
|
|
}
|
|
);
|
|
await CONTRIBUTED_TOKEN_TWO.approve(
|
|
TokenSaleContributor.address,
|
|
tokenTwoContributionAmount[1],
|
|
{
|
|
from: BUYER_ONE,
|
|
}
|
|
);
|
|
|
|
// perform "kyc" and contribute tokens to the sale for BUYER_ONE
|
|
const kycSig1 = await signContribution(
|
|
CONDUCTOR_BYTES32_ADDRESS,
|
|
SALE_2_ID,
|
|
TOKEN_ONE_INDEX,
|
|
tokenOneContributionAmount[0],
|
|
BUYER_ONE,
|
|
kycSignerPK
|
|
);
|
|
await initialized.methods
|
|
.contribute(
|
|
SALE_2_ID,
|
|
TOKEN_ONE_INDEX,
|
|
parseInt(tokenOneContributionAmount[0]),
|
|
kycSig1
|
|
)
|
|
.send({
|
|
from: BUYER_ONE,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
const kycSig2 = await signContribution(
|
|
CONDUCTOR_BYTES32_ADDRESS,
|
|
SALE_2_ID,
|
|
TOKEN_ONE_INDEX,
|
|
tokenOneContributionAmount[1],
|
|
BUYER_ONE,
|
|
kycSignerPK
|
|
);
|
|
await initialized.methods
|
|
.contribute(
|
|
SALE_2_ID,
|
|
TOKEN_ONE_INDEX,
|
|
parseInt(tokenOneContributionAmount[1]),
|
|
kycSig2
|
|
)
|
|
.send({
|
|
from: BUYER_ONE,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
const kycSig3 = await signContribution(
|
|
CONDUCTOR_BYTES32_ADDRESS,
|
|
SALE_2_ID,
|
|
TOKEN_TWO_INDEX,
|
|
tokenTwoContributionAmount[1],
|
|
BUYER_ONE,
|
|
kycSignerPK
|
|
);
|
|
await initialized.methods
|
|
.contribute(
|
|
SALE_2_ID,
|
|
TOKEN_TWO_INDEX,
|
|
parseInt(tokenTwoContributionAmount[1]),
|
|
kycSig3
|
|
)
|
|
.send({
|
|
from: BUYER_ONE,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
// perform "kyc" and contribute tokens to the sale for BUYER_TWO
|
|
const kycSig4 = await signContribution(
|
|
CONDUCTOR_BYTES32_ADDRESS,
|
|
SALE_2_ID,
|
|
TOKEN_TWO_INDEX,
|
|
tokenTwoContributionAmount[0],
|
|
BUYER_TWO,
|
|
kycSignerPK
|
|
);
|
|
await initialized.methods
|
|
.contribute(
|
|
SALE_2_ID,
|
|
TOKEN_TWO_INDEX,
|
|
parseInt(tokenTwoContributionAmount[0]),
|
|
kycSig4
|
|
)
|
|
.send({
|
|
from: BUYER_TWO,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
// verify getSaleTotalContribution after contributing
|
|
const totalContributionsTokenOne = await initialized.methods
|
|
.getSaleTotalContribution(SALE_2_ID, TOKEN_ONE_INDEX)
|
|
.call();
|
|
const totalContributionsTokenTwo = await initialized.methods
|
|
.getSaleTotalContribution(SALE_2_ID, TOKEN_TWO_INDEX)
|
|
.call();
|
|
|
|
assert.equal(
|
|
totalContributionsTokenOne,
|
|
parseInt(tokenOneContributionAmount[0]) +
|
|
parseInt(tokenOneContributionAmount[1])
|
|
);
|
|
assert.equal(
|
|
totalContributionsTokenTwo,
|
|
parseInt(tokenTwoContributionAmount[0]) +
|
|
parseInt(tokenTwoContributionAmount[1])
|
|
);
|
|
|
|
// verify getSaleContribution
|
|
const buyerOneContributionTokenOne = await initialized.methods
|
|
.getSaleContribution(SALE_2_ID, TOKEN_ONE_INDEX, BUYER_ONE)
|
|
.call();
|
|
const buyerOneContributionTokenTwo = await initialized.methods
|
|
.getSaleContribution(SALE_2_ID, TOKEN_TWO_INDEX, BUYER_ONE)
|
|
.call();
|
|
const buyerTwoContribution = await initialized.methods
|
|
.getSaleContribution(SALE_2_ID, TOKEN_TWO_INDEX, BUYER_TWO)
|
|
.call();
|
|
|
|
assert.equal(
|
|
buyerOneContributionTokenOne,
|
|
parseInt(tokenOneContributionAmount[0]) +
|
|
parseInt(tokenOneContributionAmount[1])
|
|
);
|
|
assert.equal(
|
|
buyerOneContributionTokenTwo,
|
|
parseInt(tokenTwoContributionAmount[1])
|
|
);
|
|
assert.equal(buyerTwoContribution, parseInt(tokenTwoContributionAmount[0]));
|
|
});
|
|
|
|
let CONTRIBUTIONS_PAYLOAD_2;
|
|
|
|
it("should attest contributions for second sale correctly", async function() {
|
|
await wait(10);
|
|
|
|
// test variables
|
|
const tokenOneContributionAmount = 1000;
|
|
const tokenTwoContributionAmount = 200;
|
|
const acceptedTokenLength = 2;
|
|
const payloadIdType2 = "02";
|
|
|
|
const initialized = new web3.eth.Contract(
|
|
ContributorImplementationFullABI,
|
|
TokenSaleContributor.address
|
|
);
|
|
|
|
// attest contributions
|
|
await initialized.methods.attestContributions(SALE_2_ID).send({
|
|
from: BUYER_ONE,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
const log = (
|
|
await WORMHOLE.getPastEvents("LogMessagePublished", {
|
|
fromBlock: "latest",
|
|
})
|
|
)[0].returnValues;
|
|
|
|
assert.equal(log.sender, TokenSaleContributor.address);
|
|
|
|
// payload id
|
|
let index = 2;
|
|
assert.equal(log.payload.substr(index, 2), payloadIdType2);
|
|
index += 2;
|
|
|
|
// sale id
|
|
assert.equal(parseInt(log.payload.substr(index, 64), 16), SALE_2_ID);
|
|
index += 64;
|
|
|
|
// chain id
|
|
assert.equal(
|
|
log.payload.substr(index, 4),
|
|
web3.eth.abi.encodeParameter("uint16", 2).substring(2 + 64 - 4)
|
|
);
|
|
index += 4;
|
|
|
|
// tokens length
|
|
assert.equal(
|
|
parseInt(log.payload.substr(index, 2), 16),
|
|
acceptedTokenLength
|
|
);
|
|
index += 2;
|
|
|
|
// token index
|
|
assert.equal(
|
|
log.payload.substr(index, 2),
|
|
web3.eth.abi.encodeParameter("uint8", 0).substring(2 + 64 - 2)
|
|
);
|
|
index += 2;
|
|
|
|
// amount
|
|
assert.equal(
|
|
parseInt(log.payload.substr(index, 64), 16),
|
|
tokenOneContributionAmount
|
|
);
|
|
index += 64;
|
|
|
|
// token index
|
|
assert.equal(
|
|
log.payload.substr(index, 2),
|
|
web3.eth.abi.encodeParameter("uint8", 1).substring(2 + 64 - 2)
|
|
);
|
|
index += 2;
|
|
|
|
// amount
|
|
assert.equal(
|
|
parseInt(log.payload.substr(index, 64), 16),
|
|
tokenTwoContributionAmount
|
|
);
|
|
index += 64;
|
|
|
|
assert.equal(log.payload.length, index);
|
|
|
|
CONTRIBUTIONS_PAYLOAD_2 = log.payload.toString();
|
|
});
|
|
|
|
it("conductor should collect second sale contributions correctly", async function() {
|
|
// test variables
|
|
const tokenOneContributionAmount = 1000;
|
|
const tokenTwoContributionAmount = 200;
|
|
|
|
const initialized = new web3.eth.Contract(
|
|
ConductorImplementationFullABI,
|
|
TokenSaleConductor.address
|
|
);
|
|
|
|
// verify saleContributionIsCollected getter before calling contribute
|
|
const isContributionOneCollectedBefore = await initialized.methods
|
|
.saleContributionIsCollected(SALE_2_ID, TOKEN_ONE_INDEX)
|
|
.call();
|
|
const isContributionTwoCollectedBefore = await initialized.methods
|
|
.saleContributionIsCollected(SALE_2_ID, TOKEN_TWO_INDEX)
|
|
.call();
|
|
|
|
assert.ok(!isContributionOneCollectedBefore);
|
|
assert.ok(!isContributionTwoCollectedBefore);
|
|
|
|
const vm = await signAndEncodeVM(
|
|
1,
|
|
1,
|
|
TEST_CHAIN_ID,
|
|
"0x000000000000000000000000" + TokenSaleContributor.address.substr(2),
|
|
0,
|
|
CONTRIBUTIONS_PAYLOAD_2,
|
|
[testSigner1PK],
|
|
0,
|
|
0
|
|
);
|
|
|
|
// collect the contributions
|
|
await initialized.methods.collectContribution("0x" + vm).send({
|
|
from: SELLER,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
// verify saleContributionIsCollected getter after calling contribute
|
|
const isContributionOneCollectedAfter = await initialized.methods
|
|
.saleContributionIsCollected(SALE_2_ID, TOKEN_ONE_INDEX)
|
|
.call();
|
|
const isContributionTwoCollectedAfter = await initialized.methods
|
|
.saleContributionIsCollected(SALE_2_ID, TOKEN_TWO_INDEX)
|
|
.call();
|
|
|
|
assert.ok(isContributionOneCollectedAfter);
|
|
assert.ok(isContributionTwoCollectedAfter);
|
|
|
|
// verify saleContributions getter
|
|
const contributions = await initialized.methods
|
|
.saleContributions(SALE_2_ID)
|
|
.call();
|
|
|
|
assert.equal(contributions[0], tokenOneContributionAmount);
|
|
assert.equal(contributions[1], tokenTwoContributionAmount);
|
|
});
|
|
|
|
let SALE_SEALED_PAYLOAD_2;
|
|
|
|
it("conductor should abort the second sale correctly", async function() {
|
|
// test variables
|
|
const expectedContributorBalance = "600";
|
|
const expectedConductorBalance = "1000";
|
|
const payloadIdType4 = "04";
|
|
|
|
const initialized = new web3.eth.Contract(
|
|
ConductorImplementationFullABI,
|
|
TokenSaleConductor.address
|
|
);
|
|
|
|
const actualContributorBalanceBefore = await SOLD_TOKEN.balanceOf(
|
|
TokenSaleContributor.address
|
|
);
|
|
const actualConductorBalanceBefore = await SOLD_TOKEN.balanceOf(
|
|
TokenSaleConductor.address
|
|
);
|
|
|
|
// confirm that the sale is not aborted yet
|
|
const saleBefore = await initialized.methods.sales(SALE_2_ID).call();
|
|
|
|
assert.ok(!saleBefore.isAborted);
|
|
|
|
// contributor balance is 500 before because of the reverted transaction
|
|
// in "allocation should only be claimable once"
|
|
assert.equal(actualContributorBalanceBefore, expectedContributorBalance);
|
|
assert.equal(actualConductorBalanceBefore, expectedConductorBalance);
|
|
|
|
await initialized.methods.sealSale(SALE_2_ID).send({
|
|
from: SELLER,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
const actualContributorBalanceAfter = await SOLD_TOKEN.balanceOf(
|
|
TokenSaleContributor.address
|
|
);
|
|
const actualConductorBalanceAfter = await SOLD_TOKEN.balanceOf(
|
|
TokenSaleConductor.address
|
|
);
|
|
|
|
// make sure balances haven't changed
|
|
assert.equal(actualContributorBalanceAfter, expectedContributorBalance);
|
|
assert.equal(actualConductorBalanceAfter, expectedConductorBalance);
|
|
|
|
const log = (
|
|
await WORMHOLE.getPastEvents("LogMessagePublished", {
|
|
fromBlock: "latest",
|
|
})
|
|
)[0].returnValues;
|
|
|
|
// verify sale sealed payload
|
|
SALE_SEALED_PAYLOAD_2 = log.payload;
|
|
|
|
// payload id
|
|
let index = 2;
|
|
assert.equal(SALE_SEALED_PAYLOAD_2.substr(index, 2), payloadIdType4);
|
|
index += 2;
|
|
|
|
// sale id
|
|
assert.equal(
|
|
parseInt(SALE_SEALED_PAYLOAD_2.substr(index, 64), 16),
|
|
SALE_2_ID
|
|
);
|
|
index += 64;
|
|
|
|
// confirm that the sale is aborted
|
|
const saleAfter = await initialized.methods.sales(SALE_2_ID).call();
|
|
|
|
assert.ok(saleAfter.isAborted);
|
|
});
|
|
|
|
it("contributor should abort second sale correctly", async function() {
|
|
const initialized = new web3.eth.Contract(
|
|
ContributorImplementationFullABI,
|
|
TokenSaleContributor.address
|
|
);
|
|
|
|
// verify getSaleStatus before aborting in contributor
|
|
const statusBefore = await initialized.methods
|
|
.getSaleStatus(SALE_2_ID)
|
|
.call();
|
|
|
|
assert.ok(!statusBefore.isAborted);
|
|
assert.ok(!statusBefore.isSealed);
|
|
|
|
const vm = await signAndEncodeVM(
|
|
1,
|
|
1,
|
|
TEST_CHAIN_ID,
|
|
"0x000000000000000000000000" + TokenSaleConductor.address.substr(2),
|
|
0,
|
|
SALE_SEALED_PAYLOAD_2,
|
|
[testSigner1PK],
|
|
0,
|
|
0
|
|
);
|
|
|
|
// abort the sale
|
|
await initialized.methods.saleAborted("0x" + vm).send({
|
|
from: BUYER_ONE,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
// confirm that saleAborted was set to true
|
|
const statusAfter = await initialized.methods
|
|
.getSaleStatus(SALE_2_ID)
|
|
.call();
|
|
|
|
assert.ok(statusAfter.isAborted);
|
|
assert.ok(!statusAfter.isSealed);
|
|
});
|
|
|
|
it("conductor should distribute refund to refundRecipient correctly", async function() {
|
|
// test variables
|
|
const expectedConductorBalanceBefore = "1000";
|
|
const expectedSellerBalanceBefore = "0";
|
|
const expectedConductorBalanceAfter = "0";
|
|
const expectedSellerBalanceAfter = "1000";
|
|
|
|
const initialized = new web3.eth.Contract(
|
|
ConductorImplementationFullABI,
|
|
TokenSaleConductor.address
|
|
);
|
|
|
|
// confirm that refundIsClaimed is false
|
|
const saleBefore = await initialized.methods.sales(SALE_2_ID).call();
|
|
|
|
assert.ok(!saleBefore.refundIsClaimed);
|
|
|
|
// check starting balances
|
|
const actualConductorBalanceBefore = await SOLD_TOKEN.balanceOf(
|
|
TokenSaleConductor.address
|
|
);
|
|
const actualSellerBalanceBefore = await SOLD_TOKEN.balanceOf(
|
|
SALE_2_REFUND_RECIPIENT
|
|
);
|
|
|
|
assert.equal(actualConductorBalanceBefore, expectedConductorBalanceBefore);
|
|
assert.equal(actualSellerBalanceBefore, expectedSellerBalanceBefore);
|
|
|
|
// claim the sale token refund
|
|
await initialized.methods.claimRefund(SALE_2_ID).send({
|
|
from: BUYER_ONE, // confirm that it's permissionless
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
// make sure new balances are correct
|
|
const actualConductorBalanceAfter = await SOLD_TOKEN.balanceOf(
|
|
TokenSaleConductor.address
|
|
);
|
|
const actualSellerBalanceAfter = await SOLD_TOKEN.balanceOf(
|
|
SALE_2_REFUND_RECIPIENT
|
|
);
|
|
|
|
assert.equal(actualConductorBalanceAfter, expectedConductorBalanceAfter);
|
|
assert.equal(actualSellerBalanceAfter, expectedSellerBalanceAfter);
|
|
|
|
// confirm that refundClaimed was set to true
|
|
const saleAfter = await initialized.methods.sales(SALE_2_ID).call();
|
|
|
|
assert.ok(saleAfter.refundIsClaimed);
|
|
|
|
// send refunded tokens back to SELLER account
|
|
await SOLD_TOKEN.approve(SELLER, actualSellerBalanceAfter, {
|
|
from: SALE_2_REFUND_RECIPIENT,
|
|
});
|
|
await SOLD_TOKEN.transferFrom(
|
|
SALE_2_REFUND_RECIPIENT,
|
|
SELLER,
|
|
actualSellerBalanceAfter
|
|
);
|
|
});
|
|
|
|
let ONE_REFUND_SNAPSHOT;
|
|
|
|
it("contributor should distribute refunds to contributors correctly", async function() {
|
|
// test variables
|
|
const expectedContributorTokenOneBalanceBefore = "1000";
|
|
const expectedContributorTokenTwoBalanceBefore = "200";
|
|
const expectedContributorTokenOneBalanceAfter = "0";
|
|
const expectedContributorTokenTwoBalanceAfter = "0";
|
|
const expectedBuyerOneTokenOneBalanceBefore = "9000";
|
|
const expectedBuyerOneTokenTwoBalanceBefore = "2400";
|
|
const expectedBuyerTwoBalanceBefore = "14900";
|
|
const expectedBuyerOneTokenOneBalanceAfter = "10000";
|
|
const expectedBuyerOneTokenTwoBalanceAfter = "2500";
|
|
const expectedBuyerTwoBalanceAfter = "15000";
|
|
|
|
const initialized = new web3.eth.Contract(
|
|
ContributorImplementationFullABI,
|
|
TokenSaleContributor.address
|
|
);
|
|
|
|
// confirm refundIsClaimed is set to false
|
|
const buyerOneHasClaimedRefundBefore = await initialized.methods
|
|
.refundIsClaimed(SALE_2_ID, TOKEN_ONE_INDEX, BUYER_ONE)
|
|
.call();
|
|
const buyerTwoHasClaimedRefundBefore = await initialized.methods
|
|
.refundIsClaimed(SALE_2_ID, TOKEN_TWO_INDEX, BUYER_TWO)
|
|
.call();
|
|
|
|
assert.ok(!buyerOneHasClaimedRefundBefore);
|
|
assert.ok(!buyerTwoHasClaimedRefundBefore);
|
|
|
|
// check balances of contributed tokens on the contributor
|
|
const actualContributorTokenOneBalanceBefore = await CONTRIBUTED_TOKEN_ONE.balanceOf(
|
|
TokenSaleContributor.address
|
|
);
|
|
const actualContributorTokenTwoBalanceBefore = await CONTRIBUTED_TOKEN_TWO.balanceOf(
|
|
TokenSaleContributor.address
|
|
);
|
|
|
|
assert.equal(
|
|
actualContributorTokenOneBalanceBefore,
|
|
expectedContributorTokenOneBalanceBefore
|
|
);
|
|
assert.equal(
|
|
actualContributorTokenTwoBalanceBefore,
|
|
expectedContributorTokenTwoBalanceBefore
|
|
);
|
|
|
|
// check buyer balances
|
|
const actualBuyerOneTokenOneBalanceBefore = await CONTRIBUTED_TOKEN_ONE.balanceOf(
|
|
BUYER_ONE
|
|
);
|
|
const actualBuyerOneTokenTwoBalanceBefore = await CONTRIBUTED_TOKEN_TWO.balanceOf(
|
|
BUYER_ONE
|
|
);
|
|
const actualBuyerTwoBalanceBefore = await CONTRIBUTED_TOKEN_TWO.balanceOf(
|
|
BUYER_TWO
|
|
);
|
|
|
|
assert.equal(
|
|
actualBuyerOneTokenOneBalanceBefore,
|
|
expectedBuyerOneTokenOneBalanceBefore
|
|
);
|
|
assert.equal(
|
|
actualBuyerOneTokenTwoBalanceBefore,
|
|
expectedBuyerOneTokenTwoBalanceBefore
|
|
);
|
|
assert.equal(actualBuyerTwoBalanceBefore, expectedBuyerTwoBalanceBefore);
|
|
|
|
// BUYER_ONE/BUYER_TWO claims refunds
|
|
await initialized.methods.claimRefund(SALE_2_ID, TOKEN_ONE_INDEX).send({
|
|
from: BUYER_ONE,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
// snapshot to test trying to claim refund 2x
|
|
ONE_REFUND_SNAPSHOT = await snapshot();
|
|
|
|
await initialized.methods.claimRefund(SALE_2_ID, TOKEN_TWO_INDEX).send({
|
|
from: BUYER_ONE,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
await initialized.methods.claimRefund(SALE_2_ID, TOKEN_TWO_INDEX).send({
|
|
from: BUYER_TWO,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
// check balances of contributed tokens on contributor
|
|
const actualContributorTokenOneBalanceAfter = await CONTRIBUTED_TOKEN_ONE.balanceOf(
|
|
TokenSaleContributor.address
|
|
);
|
|
const actualContributorTokenTwoBalanceAfter = await CONTRIBUTED_TOKEN_TWO.balanceOf(
|
|
TokenSaleContributor.address
|
|
);
|
|
|
|
assert.equal(
|
|
actualContributorTokenOneBalanceAfter,
|
|
expectedContributorTokenOneBalanceAfter
|
|
);
|
|
assert.equal(
|
|
actualContributorTokenTwoBalanceAfter,
|
|
expectedContributorTokenTwoBalanceAfter
|
|
);
|
|
|
|
// check buyer balances after claiming refund
|
|
const actualBuyerOneTokenOneBalanceAfter = await CONTRIBUTED_TOKEN_ONE.balanceOf(
|
|
BUYER_ONE
|
|
);
|
|
const actualBuyerOneTokenTwoBalanceAfter = await CONTRIBUTED_TOKEN_TWO.balanceOf(
|
|
BUYER_ONE
|
|
);
|
|
const actualBuyerTwoBalanceAfter = await CONTRIBUTED_TOKEN_TWO.balanceOf(
|
|
BUYER_TWO
|
|
);
|
|
|
|
assert.equal(
|
|
actualBuyerOneTokenOneBalanceAfter,
|
|
expectedBuyerOneTokenOneBalanceAfter
|
|
);
|
|
assert.equal(
|
|
actualBuyerOneTokenTwoBalanceAfter,
|
|
expectedBuyerOneTokenTwoBalanceAfter
|
|
);
|
|
assert.equal(actualBuyerTwoBalanceAfter, expectedBuyerTwoBalanceAfter);
|
|
|
|
// confirm refundIsClaimed is set to true
|
|
const buyerOneHasClaimedRefundAfter = await initialized.methods
|
|
.refundIsClaimed(SALE_2_ID, TOKEN_ONE_INDEX, BUYER_ONE)
|
|
.call();
|
|
const buyerTwoHasClaimedRefundAfter = await initialized.methods
|
|
.refundIsClaimed(SALE_2_ID, TOKEN_TWO_INDEX, BUYER_TWO)
|
|
.call();
|
|
|
|
assert.ok(buyerOneHasClaimedRefundAfter);
|
|
assert.ok(buyerTwoHasClaimedRefundAfter);
|
|
});
|
|
|
|
it("refund should only be claimable once in contributor", async function() {
|
|
const initialized = new web3.eth.Contract(
|
|
ContributorImplementationFullABI,
|
|
TokenSaleContributor.address
|
|
);
|
|
|
|
await revert(ONE_REFUND_SNAPSHOT);
|
|
|
|
let failed = false;
|
|
try {
|
|
await initialized.methods.claimRefund(SALE_2_ID, TOKEN_ONE_INDEX).send({
|
|
from: BUYER_ONE,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
} catch (e) {
|
|
assert.equal(
|
|
e.message,
|
|
"Returned error: VM Exception while processing transaction: revert refund already claimed"
|
|
);
|
|
failed = true;
|
|
}
|
|
|
|
assert.ok(failed);
|
|
});
|
|
|
|
it("refund should only be claimable once in conductor", async function() {
|
|
const initialized = new web3.eth.Contract(
|
|
ConductorImplementationFullABI,
|
|
TokenSaleConductor.address
|
|
);
|
|
|
|
let failed = false;
|
|
try {
|
|
// claim the sale token refund
|
|
await initialized.methods.claimRefund(SALE_2_ID).send({
|
|
from: SELLER,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
} catch (e) {
|
|
assert.equal(
|
|
e.message,
|
|
"Returned error: VM Exception while processing transaction: revert already claimed"
|
|
);
|
|
failed = true;
|
|
}
|
|
|
|
assert.ok(failed);
|
|
});
|
|
|
|
let SALE_3_START;
|
|
let SALE_3_END;
|
|
let SALE_3_INIT_PAYLOAD;
|
|
let SALE_3_ID;
|
|
|
|
it("create a third sale correctly and attest over wormhole", async function() {
|
|
console.log(
|
|
"\n -------------------------- Sale Test #3 (Aborted Early) --------------------------"
|
|
);
|
|
|
|
// test variables
|
|
const current_block = await web3.eth.getBlock("latest");
|
|
SALE_3_START = current_block.timestamp + 5;
|
|
SALE_3_END = SALE_3_START + 8;
|
|
const saleTokenAmount = "1000";
|
|
const minimumTokenRaise = "2000";
|
|
const maximumTokenRaise = "30000";
|
|
const tokenOneConversionRate = "1000000000000000000";
|
|
const tokenTwoConversionRate = "2000000000000000000";
|
|
const saleRecipient = accounts[0];
|
|
const refundRecipient = accounts[0];
|
|
const acceptedTokenLength = 2;
|
|
const payloadIdType1 = "01";
|
|
|
|
const initialized = new web3.eth.Contract(
|
|
ConductorImplementationFullABI,
|
|
TokenSaleConductor.address
|
|
);
|
|
|
|
await SOLD_TOKEN.approve(TokenSaleConductor.address, saleTokenAmount);
|
|
|
|
// create array (solidity struct) for sale params
|
|
const saleParams = [
|
|
SOLD_TOKEN_BYTES32_ADDRESS,
|
|
TEST_CHAIN_ID,
|
|
saleTokenAmount,
|
|
minimumTokenRaise,
|
|
maximumTokenRaise,
|
|
SALE_3_START,
|
|
SALE_3_END,
|
|
saleRecipient,
|
|
refundRecipient,
|
|
];
|
|
|
|
// create accepted tokens array
|
|
const acceptedTokens = [
|
|
[
|
|
TEST_CHAIN_ID,
|
|
"0x000000000000000000000000" + CONTRIBUTED_TOKEN_ONE.address.substr(2),
|
|
tokenOneConversionRate,
|
|
],
|
|
[
|
|
TEST_CHAIN_ID,
|
|
"0x000000000000000000000000" + CONTRIBUTED_TOKEN_TWO.address.substr(2),
|
|
tokenTwoConversionRate,
|
|
],
|
|
];
|
|
|
|
// create a third sale
|
|
await initialized.methods.createSale(saleParams, acceptedTokens).send({
|
|
value: "0",
|
|
from: SELLER,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
const log = (
|
|
await WORMHOLE.getPastEvents("LogMessagePublished", {
|
|
fromBlock: "latest",
|
|
})
|
|
)[0].returnValues;
|
|
|
|
assert.equal(log.sender, TokenSaleConductor.address);
|
|
|
|
// payload id
|
|
let index = 2;
|
|
assert.equal(log.payload.substr(index, 2), payloadIdType1);
|
|
index += 2;
|
|
|
|
// sale id, should == 1 since it's the third sale
|
|
SALE_3_ID = SALE_2_ID + 1;
|
|
assert.equal(parseInt(log.payload.substr(index, 64), 16), SALE_3_ID);
|
|
index += 64;
|
|
|
|
// token address
|
|
assert.equal(
|
|
log.payload.substr(index, 64),
|
|
web3.eth.abi.encodeParameter("address", SOLD_TOKEN.address).substring(2)
|
|
);
|
|
index += 64;
|
|
|
|
// token chain
|
|
assert.equal(
|
|
log.payload.substr(index, 4),
|
|
web3.eth.abi
|
|
.encodeParameter("uint16", TEST_CHAIN_ID)
|
|
.substring(2 + 64 - 4)
|
|
);
|
|
index += 4;
|
|
|
|
// token decimals
|
|
assert.equal(
|
|
parseInt(log.payload.substr(index, 2), 16),
|
|
SOLD_TOKEN_DECIMALS
|
|
);
|
|
index += 2;
|
|
|
|
// token amount
|
|
assert.equal(
|
|
parseInt(log.payload.substr(index, 64), 16),
|
|
parseInt(saleTokenAmount)
|
|
);
|
|
index += 64;
|
|
|
|
// min raise amount
|
|
assert.equal(
|
|
parseInt(log.payload.substr(index, 64), 16),
|
|
parseInt(minimumTokenRaise)
|
|
);
|
|
index += 64;
|
|
|
|
// max raise amount
|
|
assert.equal(
|
|
parseInt(log.payload.substr(index, 64), 16),
|
|
parseInt(maximumTokenRaise)
|
|
);
|
|
index += 64;
|
|
|
|
// timestamp start
|
|
assert.equal(parseInt(log.payload.substr(index, 64), 16), SALE_3_START);
|
|
index += 64;
|
|
|
|
// timestamp end
|
|
assert.equal(parseInt(log.payload.substr(index, 64), 16), SALE_3_END);
|
|
index += 64;
|
|
|
|
// accepted tokens length
|
|
assert.equal(
|
|
parseInt(log.payload.substr(index, 2), 16),
|
|
acceptedTokenLength
|
|
);
|
|
index += 2;
|
|
|
|
// token address
|
|
assert.equal(
|
|
log.payload.substr(index, 64),
|
|
web3.eth.abi
|
|
.encodeParameter("address", CONTRIBUTED_TOKEN_ONE.address)
|
|
.substring(2)
|
|
);
|
|
index += 64;
|
|
|
|
// token chain
|
|
assert.equal(
|
|
log.payload.substr(index, 4),
|
|
web3.eth.abi
|
|
.encodeParameter("uint16", TEST_CHAIN_ID)
|
|
.substring(2 + 64 - 4)
|
|
);
|
|
index += 4;
|
|
|
|
// conversion rate
|
|
assert.equal(
|
|
parseInt(log.payload.substr(index, 32), 16),
|
|
parseInt(tokenOneConversionRate)
|
|
);
|
|
index += 32;
|
|
|
|
// token address
|
|
assert.equal(
|
|
log.payload.substr(index, 64),
|
|
web3.eth.abi
|
|
.encodeParameter("address", CONTRIBUTED_TOKEN_TWO.address)
|
|
.substring(2)
|
|
);
|
|
index += 64;
|
|
|
|
// token chain
|
|
assert.equal(
|
|
log.payload.substr(index, 4),
|
|
web3.eth.abi
|
|
.encodeParameter("uint16", TEST_CHAIN_ID)
|
|
.substring(2 + 64 - 4)
|
|
);
|
|
index += 4;
|
|
|
|
// conversion rate
|
|
assert.equal(
|
|
parseInt(log.payload.substr(index, 32), 16),
|
|
parseInt(tokenTwoConversionRate)
|
|
);
|
|
index += 32;
|
|
|
|
// recipient of proceeds
|
|
assert.equal(
|
|
log.payload.substr(index, 64),
|
|
web3.eth.abi.encodeParameter("address", saleRecipient).substring(2)
|
|
);
|
|
index += 64;
|
|
|
|
// refund recipient in case the sale is aborted
|
|
assert.equal(
|
|
log.payload.substr(index, 64),
|
|
web3.eth.abi.encodeParameter("address", refundRecipient).substring(2)
|
|
);
|
|
index += 64;
|
|
|
|
assert.equal(log.payload.length, index);
|
|
SALE_3_INIT_PAYLOAD = log.payload.toString();
|
|
|
|
// verify that getNextSaleId is correct
|
|
const nextSaleId = await initialized.methods.getNextSaleId().call();
|
|
|
|
assert.equal(nextSaleId, SALE_3_ID + 1);
|
|
|
|
// confirm that the localTokenAddress was saved correctl
|
|
const sale = await initialized.methods.sales(SALE_3_ID).call();
|
|
|
|
assert.equal(SOLD_TOKEN.address, sale.localTokenAddress);
|
|
});
|
|
|
|
it("should init a third sale in the contributor", async function() {
|
|
// test variables
|
|
const saleTokenAmount = 1000;
|
|
const minimumTokenRaise = 2000;
|
|
const maximumTokenRaise = 30000;
|
|
const tokenOneConversionRate = 1000000000000000000;
|
|
const tokenTwoConversionRate = 2000000000000000000;
|
|
const saleRecipient = accounts[0];
|
|
const refundRecipient = accounts[0];
|
|
|
|
const initialized = new web3.eth.Contract(
|
|
ContributorImplementationFullABI,
|
|
TokenSaleContributor.address
|
|
);
|
|
|
|
const vm = await signAndEncodeVM(
|
|
1,
|
|
1,
|
|
TEST_CHAIN_ID,
|
|
"0x000000000000000000000000" + TokenSaleConductor.address.substr(2),
|
|
0,
|
|
SALE_3_INIT_PAYLOAD,
|
|
[testSigner1PK],
|
|
0,
|
|
0
|
|
);
|
|
|
|
// initialize the third sale
|
|
await initialized.methods.initSale("0x" + vm).send({
|
|
from: SELLER,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
// verify sale getter
|
|
const sale = await initialized.methods.sales(SALE_3_ID).call();
|
|
|
|
assert.equal(sale.saleID, SALE_3_ID);
|
|
assert.equal(
|
|
sale.tokenAddress.substring(2),
|
|
web3.eth.abi.encodeParameter("address", SOLD_TOKEN.address).substring(2)
|
|
);
|
|
assert.equal(sale.tokenChain, TEST_CHAIN_ID);
|
|
assert.equal(sale.tokenAmount, saleTokenAmount);
|
|
assert.equal(sale.minRaise, minimumTokenRaise);
|
|
assert.equal(sale.maxRaise, parseInt(maximumTokenRaise));
|
|
assert.equal(sale.saleStart, SALE_3_START);
|
|
assert.equal(sale.saleEnd, SALE_3_END);
|
|
assert.equal(
|
|
sale.acceptedTokensAddresses[TOKEN_ONE_INDEX].substring(2),
|
|
web3.eth.abi
|
|
.encodeParameter("address", CONTRIBUTED_TOKEN_ONE.address)
|
|
.substring(2)
|
|
);
|
|
assert.equal(sale.acceptedTokensChains[TOKEN_ONE_INDEX], TEST_CHAIN_ID);
|
|
assert.equal(
|
|
sale.acceptedTokensConversionRates[TOKEN_ONE_INDEX],
|
|
tokenOneConversionRate
|
|
);
|
|
assert.equal(
|
|
sale.acceptedTokensAddresses[TOKEN_TWO_INDEX].substring(2),
|
|
web3.eth.abi
|
|
.encodeParameter("address", CONTRIBUTED_TOKEN_TWO.address)
|
|
.substring(2)
|
|
);
|
|
assert.equal(sale.acceptedTokensChains[TOKEN_TWO_INDEX], TEST_CHAIN_ID);
|
|
assert.equal(
|
|
sale.acceptedTokensConversionRates[TOKEN_TWO_INDEX],
|
|
tokenTwoConversionRate
|
|
);
|
|
assert.equal(
|
|
sale.recipient.substring(2),
|
|
web3.eth.abi.encodeParameter("address", saleRecipient).substring(2)
|
|
);
|
|
assert.equal(
|
|
sale.refundRecipient.substring(2),
|
|
web3.eth.abi.encodeParameter("address", refundRecipient).substring(2)
|
|
);
|
|
assert.equal(sale.allocations[TOKEN_ONE_INDEX], 0);
|
|
assert.equal(sale.allocations[TOKEN_TWO_INDEX], 0);
|
|
assert.equal(sale.excessContributions[TOKEN_ONE_INDEX], 0);
|
|
assert.equal(sale.excessContributions[TOKEN_TWO_INDEX], 0);
|
|
assert.ok(!sale.isSealed);
|
|
assert.ok(!sale.isAborted);
|
|
|
|
// verify getsaleAcceptedTokenInfo getter
|
|
const tokenOneInfo = await initialized.methods
|
|
.getSaleAcceptedTokenInfo(SALE_3_ID, TOKEN_ONE_INDEX)
|
|
.call();
|
|
const tokenTwoInfo = await initialized.methods
|
|
.getSaleAcceptedTokenInfo(SALE_3_ID, TOKEN_TWO_INDEX)
|
|
.call();
|
|
|
|
assert.equal(
|
|
tokenOneInfo.tokenAddress.substring(2),
|
|
web3.eth.abi
|
|
.encodeParameter("address", CONTRIBUTED_TOKEN_ONE.address)
|
|
.substring(2)
|
|
);
|
|
assert.equal(tokenOneInfo.tokenChainId, TEST_CHAIN_ID);
|
|
assert.equal(tokenOneInfo.conversionRate, tokenOneConversionRate);
|
|
assert.equal(
|
|
tokenTwoInfo.tokenAddress.substring(2),
|
|
web3.eth.abi
|
|
.encodeParameter("address", CONTRIBUTED_TOKEN_TWO.address)
|
|
.substring(2)
|
|
);
|
|
assert.equal(tokenTwoInfo.tokenChainId, TEST_CHAIN_ID);
|
|
assert.equal(tokenTwoInfo.conversionRate, tokenTwoConversionRate);
|
|
|
|
// verify getSaleTimeFrame getter
|
|
const saleTimeframe = await initialized.methods
|
|
.getSaleTimeframe(SALE_3_ID)
|
|
.call();
|
|
|
|
assert.equal(saleTimeframe.start, SALE_3_START);
|
|
assert.equal(saleTimeframe.end, SALE_3_END);
|
|
|
|
// verify getSaleStatus getter
|
|
const saleStatus = await initialized.methods
|
|
.getSaleStatus(SALE_3_ID)
|
|
.call();
|
|
|
|
assert.ok(!saleStatus.isSealed);
|
|
assert.ok(!saleStatus.isAborted);
|
|
});
|
|
|
|
let SALE_SEALED_PAYLOAD_3;
|
|
|
|
it("conductor should abort sale before the third sale starts", async function() {
|
|
// test variables
|
|
const payloadIdType4 = "04";
|
|
|
|
const initialized = new web3.eth.Contract(
|
|
ConductorImplementationFullABI,
|
|
TokenSaleConductor.address
|
|
);
|
|
|
|
// verify getSaleStatus getter before aborting
|
|
const saleStatusBefore = await initialized.methods.sales(SALE_3_ID).call();
|
|
|
|
assert.ok(!saleStatusBefore.isSealed);
|
|
assert.ok(!saleStatusBefore.isAborted);
|
|
|
|
// make sure only the initiator can abort the sale early
|
|
let failed = false;
|
|
try {
|
|
await initialized.methods.abortSaleBeforeStartTime(SALE_3_ID).send({
|
|
from: BUYER_ONE,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
} catch (e) {
|
|
assert.equal(
|
|
e.message,
|
|
"Returned error: VM Exception while processing transaction: revert only initiator can abort the sale early"
|
|
);
|
|
failed = true;
|
|
}
|
|
|
|
assert.ok(failed);
|
|
|
|
// abort the sale
|
|
await initialized.methods.abortSaleBeforeStartTime(SALE_3_ID).send({
|
|
from: SELLER, // must be the sale initiator (msg.sender in createSale())
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
// grab VAA so contributor can abort the sale
|
|
const log = (
|
|
await WORMHOLE.getPastEvents("LogMessagePublished", {
|
|
fromBlock: "latest",
|
|
})
|
|
)[0].returnValues;
|
|
|
|
// verify sale sealed payload
|
|
SALE_SEALED_PAYLOAD_3 = log.payload;
|
|
|
|
// payload id
|
|
let index = 2;
|
|
assert.equal(SALE_SEALED_PAYLOAD_3.substr(index, 2), payloadIdType4);
|
|
index += 2;
|
|
|
|
// sale id
|
|
assert.equal(
|
|
parseInt(SALE_SEALED_PAYLOAD_3.substr(index, 64), 16),
|
|
SALE_3_ID
|
|
);
|
|
index += 64;
|
|
|
|
// verify getSaleStatus getter after aborting
|
|
const saleStatusAfter = await initialized.methods.sales(SALE_3_ID).call();
|
|
|
|
assert.ok(!saleStatusAfter.isSealed);
|
|
assert.ok(saleStatusAfter.isAborted);
|
|
});
|
|
|
|
it("should accept contributions after sale period starts and before aborting the sale (block timestamps out of sync test)", async function() {
|
|
// this test simulates block timestamps being out of sync cross-chain
|
|
await wait(5);
|
|
|
|
// test variables
|
|
const tokenOneContributionAmount = "100";
|
|
const tokenTwoContributionAmount = "50";
|
|
|
|
const initialized = new web3.eth.Contract(
|
|
ContributorImplementationFullABI,
|
|
TokenSaleContributor.address
|
|
);
|
|
|
|
await CONTRIBUTED_TOKEN_ONE.approve(
|
|
TokenSaleContributor.address,
|
|
tokenOneContributionAmount,
|
|
{
|
|
from: BUYER_ONE,
|
|
}
|
|
);
|
|
await CONTRIBUTED_TOKEN_TWO.approve(
|
|
TokenSaleContributor.address,
|
|
tokenTwoContributionAmount,
|
|
{
|
|
from: BUYER_TWO,
|
|
}
|
|
);
|
|
|
|
// contribute tokens to the sale
|
|
const kycSig1 = await signContribution(
|
|
CONDUCTOR_BYTES32_ADDRESS,
|
|
SALE_3_ID,
|
|
TOKEN_ONE_INDEX,
|
|
tokenOneContributionAmount,
|
|
BUYER_ONE,
|
|
kycSignerPK
|
|
);
|
|
await initialized.methods
|
|
.contribute(
|
|
SALE_3_ID,
|
|
TOKEN_ONE_INDEX,
|
|
parseInt(tokenOneContributionAmount),
|
|
kycSig1
|
|
)
|
|
.send({
|
|
from: BUYER_ONE,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
const kycSig2 = await signContribution(
|
|
CONDUCTOR_BYTES32_ADDRESS,
|
|
SALE_3_ID,
|
|
TOKEN_TWO_INDEX,
|
|
tokenTwoContributionAmount,
|
|
BUYER_TWO,
|
|
kycSignerPK
|
|
);
|
|
await initialized.methods
|
|
.contribute(
|
|
SALE_3_ID,
|
|
TOKEN_TWO_INDEX,
|
|
parseInt(tokenTwoContributionAmount),
|
|
kycSig2
|
|
)
|
|
.send({
|
|
from: BUYER_TWO,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
// verify getSaleTotalContribution after contributing
|
|
const totalContributionsTokenOne = await initialized.methods
|
|
.getSaleTotalContribution(SALE_3_ID, TOKEN_ONE_INDEX)
|
|
.call();
|
|
const totalContributionsTokenTwo = await initialized.methods
|
|
.getSaleTotalContribution(SALE_3_ID, TOKEN_TWO_INDEX)
|
|
.call();
|
|
|
|
assert.equal(
|
|
totalContributionsTokenOne,
|
|
parseInt(tokenOneContributionAmount)
|
|
);
|
|
assert.equal(
|
|
totalContributionsTokenTwo,
|
|
parseInt(tokenTwoContributionAmount)
|
|
);
|
|
|
|
// verify getSaleContribution
|
|
const buyerOneContribution = await initialized.methods
|
|
.getSaleContribution(SALE_3_ID, TOKEN_ONE_INDEX, BUYER_ONE)
|
|
.call();
|
|
const buyerTwoContribution = await initialized.methods
|
|
.getSaleContribution(SALE_3_ID, TOKEN_TWO_INDEX, BUYER_TWO)
|
|
.call();
|
|
|
|
assert.equal(buyerOneContribution, parseInt(tokenOneContributionAmount));
|
|
assert.equal(buyerTwoContribution, parseInt(tokenTwoContributionAmount));
|
|
});
|
|
|
|
it("contributor should abort third sale correctly", async function() {
|
|
const initialized = new web3.eth.Contract(
|
|
ContributorImplementationFullABI,
|
|
TokenSaleContributor.address
|
|
);
|
|
|
|
// verify getSaleStatus before aborting in contributor
|
|
const statusBefore = await initialized.methods
|
|
.getSaleStatus(SALE_3_ID)
|
|
.call();
|
|
|
|
assert.ok(!statusBefore.isAborted);
|
|
assert.ok(!statusBefore.isSealed);
|
|
|
|
const vm = await signAndEncodeVM(
|
|
1,
|
|
1,
|
|
TEST_CHAIN_ID,
|
|
"0x000000000000000000000000" + TokenSaleConductor.address.substr(2),
|
|
0,
|
|
SALE_SEALED_PAYLOAD_3,
|
|
[testSigner1PK],
|
|
0,
|
|
0
|
|
);
|
|
|
|
// abort the sale
|
|
await initialized.methods.saleAborted("0x" + vm).send({
|
|
from: BUYER_ONE,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
// confirm that saleAborted was set to true
|
|
const statusAfter = await initialized.methods
|
|
.getSaleStatus(SALE_3_ID)
|
|
.call();
|
|
|
|
assert.ok(statusAfter.isAborted);
|
|
assert.ok(!statusAfter.isSealed);
|
|
});
|
|
|
|
it("contributor should not allow contributions after sale is aborted early", async function() {
|
|
// test variables
|
|
const tokenOneContributionAmount = "100";
|
|
|
|
const initialized = new web3.eth.Contract(
|
|
ContributorImplementationFullABI,
|
|
TokenSaleContributor.address
|
|
);
|
|
|
|
await CONTRIBUTED_TOKEN_ONE.approve(
|
|
TokenSaleContributor.address,
|
|
tokenOneContributionAmount,
|
|
{
|
|
from: BUYER_ONE,
|
|
}
|
|
);
|
|
|
|
let failed = false;
|
|
try {
|
|
// try to contribute tokens to the sale
|
|
const kycSig1 = await signContribution(
|
|
CONDUCTOR_BYTES32_ADDRESS,
|
|
SALE_3_ID,
|
|
TOKEN_ONE_INDEX,
|
|
tokenOneContributionAmount,
|
|
BUYER_ONE,
|
|
kycSignerPK
|
|
);
|
|
await initialized.methods
|
|
.contribute(
|
|
SALE_3_ID,
|
|
TOKEN_ONE_INDEX,
|
|
parseInt(tokenOneContributionAmount),
|
|
kycSig1
|
|
)
|
|
.send({
|
|
from: BUYER_ONE,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
} catch (e) {
|
|
assert.equal(
|
|
e.message,
|
|
"Returned error: VM Exception while processing transaction: revert sale was aborted"
|
|
);
|
|
failed = true;
|
|
}
|
|
|
|
assert.ok(failed);
|
|
});
|
|
|
|
it("contributor should not allow contributions to be attested after sale is aborted early", async function() {
|
|
await wait(10);
|
|
|
|
const initialized = new web3.eth.Contract(
|
|
ContributorImplementationFullABI,
|
|
TokenSaleContributor.address
|
|
);
|
|
|
|
let failed = false;
|
|
try {
|
|
// attest contributions
|
|
await initialized.methods.attestContributions(SALE_2_ID).send({
|
|
from: BUYER_ONE,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
} catch (e) {
|
|
assert.equal(
|
|
e.message,
|
|
"Returned error: VM Exception while processing transaction: revert already sealed / aborted"
|
|
);
|
|
failed = true;
|
|
}
|
|
|
|
assert.ok(failed);
|
|
});
|
|
|
|
it("conductor should distribute refund to refundRecipient correctly after sale is aborted early", async function() {
|
|
// test variables
|
|
const expectedConductorBalanceBefore = "1000";
|
|
const expectedSellerBalanceBefore = "0";
|
|
const expectedConductorBalanceAfter = "0";
|
|
const expectedSellerBalanceAfter = "1000";
|
|
|
|
const initialized = new web3.eth.Contract(
|
|
ConductorImplementationFullABI,
|
|
TokenSaleConductor.address
|
|
);
|
|
|
|
// confirm that refundIsClaimed is false
|
|
const saleBefore = await initialized.methods.sales(SALE_3_ID).call();
|
|
|
|
assert.ok(!saleBefore.refundIsClaimed);
|
|
|
|
// check starting balances
|
|
const actualConductorBalanceBefore = await SOLD_TOKEN.balanceOf(
|
|
TokenSaleConductor.address
|
|
);
|
|
const actualSellerBalanceBefore = await SOLD_TOKEN.balanceOf(SELLER);
|
|
|
|
assert.equal(actualConductorBalanceBefore, expectedConductorBalanceBefore);
|
|
assert.equal(actualSellerBalanceBefore, expectedSellerBalanceBefore);
|
|
|
|
// claim the sale token refund
|
|
await initialized.methods.claimRefund(SALE_3_ID).send({
|
|
from: SELLER,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
// make sure new balances are correct
|
|
const actualConductorBalanceAfter = await SOLD_TOKEN.balanceOf(
|
|
TokenSaleConductor.address
|
|
);
|
|
const actualSellerBalanceAfter = await SOLD_TOKEN.balanceOf(SELLER);
|
|
|
|
assert.equal(actualConductorBalanceAfter, expectedConductorBalanceAfter);
|
|
assert.equal(actualSellerBalanceAfter, expectedSellerBalanceAfter);
|
|
|
|
// confirm that refundClaimed was set to true
|
|
const saleAfter = await initialized.methods.sales(SALE_3_ID).call();
|
|
|
|
assert.ok(saleAfter.refundIsClaimed);
|
|
});
|
|
|
|
it("contributor should distribute refunds to contributors correctly after sale is aborted early", async function() {
|
|
// test variables
|
|
const expectedContributorTokenOneBalanceBefore = "100"; // 100 contributed
|
|
const expectedContributorTokenTwoBalanceBefore = "250"; // 50 contributed - there is some residual amount from a previous test
|
|
const expectedBuyerOneBalanceBefore = "9900";
|
|
const expectedBuyerTwoBalanceBefore = "14850";
|
|
const expectedContributorTokenOneBalanceAfter = "0";
|
|
const expectedContributorTokenTwoBalanceAfter = "200";
|
|
const expectedBuyerOneBalanceAfter = "10000";
|
|
const expectedBuyerTwoBalanceAfter = "14900";
|
|
|
|
const initialized = new web3.eth.Contract(
|
|
ContributorImplementationFullABI,
|
|
TokenSaleContributor.address
|
|
);
|
|
|
|
// confirm refundIsClaimed is set to false
|
|
const buyerOneHasClaimedRefundBefore = await initialized.methods
|
|
.refundIsClaimed(SALE_3_ID, TOKEN_ONE_INDEX, BUYER_ONE)
|
|
.call();
|
|
const buyerTwoHasClaimedRefundBefore = await initialized.methods
|
|
.refundIsClaimed(SALE_3_ID, TOKEN_TWO_INDEX, BUYER_TWO)
|
|
.call();
|
|
|
|
assert.ok(!buyerOneHasClaimedRefundBefore);
|
|
assert.ok(!buyerTwoHasClaimedRefundBefore);
|
|
|
|
// check balances of contributed tokens for buyers and the contributor
|
|
const actualContributorTokenOneBalanceBefore = await CONTRIBUTED_TOKEN_ONE.balanceOf(
|
|
TokenSaleContributor.address
|
|
);
|
|
const actualContributorTokenTwoBalanceBefore = await CONTRIBUTED_TOKEN_TWO.balanceOf(
|
|
TokenSaleContributor.address
|
|
);
|
|
const actualBuyerOneBalanceBefore = await CONTRIBUTED_TOKEN_ONE.balanceOf(
|
|
BUYER_ONE
|
|
);
|
|
const actualBuyerTwoBalanceBefore = await CONTRIBUTED_TOKEN_TWO.balanceOf(
|
|
BUYER_TWO
|
|
);
|
|
|
|
assert.equal(
|
|
actualContributorTokenOneBalanceBefore,
|
|
expectedContributorTokenOneBalanceBefore
|
|
);
|
|
assert.equal(
|
|
actualContributorTokenTwoBalanceBefore,
|
|
expectedContributorTokenTwoBalanceBefore
|
|
);
|
|
assert.equal(actualBuyerOneBalanceBefore, expectedBuyerOneBalanceBefore);
|
|
assert.equal(actualBuyerTwoBalanceBefore, expectedBuyerTwoBalanceBefore);
|
|
|
|
// BUYER_ONE/BUYER_TWO claims refund
|
|
await initialized.methods.claimRefund(SALE_3_ID, TOKEN_ONE_INDEX).send({
|
|
from: BUYER_ONE,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
await initialized.methods.claimRefund(SALE_3_ID, TOKEN_TWO_INDEX).send({
|
|
from: BUYER_TWO,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
// check balances of contributed tokens for buyers and the contributor
|
|
const actualContributorTokenOneBalanceAfter = await CONTRIBUTED_TOKEN_ONE.balanceOf(
|
|
TokenSaleContributor.address
|
|
);
|
|
const actualContributorTokenTwoBalanceAfter = await CONTRIBUTED_TOKEN_TWO.balanceOf(
|
|
TokenSaleContributor.address
|
|
);
|
|
const actualBuyerOneBalanceAfter = await CONTRIBUTED_TOKEN_ONE.balanceOf(
|
|
BUYER_ONE
|
|
);
|
|
const actualBuyerTwoBalanceAfter = await CONTRIBUTED_TOKEN_TWO.balanceOf(
|
|
BUYER_TWO
|
|
);
|
|
|
|
assert.equal(
|
|
actualContributorTokenOneBalanceAfter,
|
|
expectedContributorTokenOneBalanceAfter
|
|
);
|
|
assert.equal(
|
|
actualContributorTokenTwoBalanceAfter,
|
|
expectedContributorTokenTwoBalanceAfter
|
|
);
|
|
assert.equal(actualBuyerOneBalanceAfter, expectedBuyerOneBalanceAfter);
|
|
assert.equal(actualBuyerTwoBalanceAfter, expectedBuyerTwoBalanceAfter);
|
|
|
|
// confirm refundIsClaimed is set to true
|
|
const buyerOneHasClaimedRefundAfter = await initialized.methods
|
|
.refundIsClaimed(SALE_3_ID, TOKEN_ONE_INDEX, BUYER_ONE)
|
|
.call();
|
|
const buyerTwoHasClaimedRefundAfter = await initialized.methods
|
|
.refundIsClaimed(SALE_3_ID, TOKEN_TWO_INDEX, BUYER_TWO)
|
|
.call();
|
|
|
|
assert.ok(buyerOneHasClaimedRefundAfter);
|
|
assert.ok(buyerTwoHasClaimedRefundAfter);
|
|
});
|
|
|
|
// more global sale test variables
|
|
let SALE_4_START;
|
|
let SALE_4_END;
|
|
let SALE_4_INIT_PAYLOAD;
|
|
let SALE_4_ID;
|
|
|
|
it("create a fourth sale correctly and attest over wormhole", async function() {
|
|
console.log(
|
|
"\n -------------------------- Sale Test #4 (Oversubscribed & Successful) --------------------------"
|
|
);
|
|
|
|
// test variables
|
|
const current_block = await web3.eth.getBlock("latest");
|
|
SALE_4_START = current_block.timestamp + 5;
|
|
SALE_4_END = SALE_4_START + 8;
|
|
|
|
const saleTokenAmount = "1000";
|
|
const minimumTokenRaise = "2000";
|
|
const maximumTokenRaise = "6000";
|
|
const tokenOneConversionRate = "1000000000000000000";
|
|
const tokenTwoConversionRate = "4000000000000000000";
|
|
const saleRecipient = accounts[0];
|
|
const refundRecipient = accounts[0];
|
|
const acceptedTokenLength = 2;
|
|
const payloadIdType1 = "01";
|
|
const saleTokenMintAmount = "2000";
|
|
|
|
// mint some more sale tokens
|
|
await SOLD_TOKEN.mint(SELLER, saleTokenMintAmount);
|
|
await SOLD_TOKEN.approve(TokenSaleConductor.address, saleTokenAmount);
|
|
|
|
const initialized = new web3.eth.Contract(
|
|
ConductorImplementationFullABI,
|
|
TokenSaleConductor.address
|
|
);
|
|
|
|
// create array (struct) for sale params
|
|
const saleParams = [
|
|
SOLD_TOKEN_BYTES32_ADDRESS,
|
|
TEST_CHAIN_ID,
|
|
saleTokenAmount,
|
|
minimumTokenRaise,
|
|
maximumTokenRaise,
|
|
SALE_4_START,
|
|
SALE_4_END,
|
|
saleRecipient,
|
|
refundRecipient,
|
|
];
|
|
|
|
// create accepted tokens array
|
|
const acceptedTokens = [
|
|
[
|
|
TEST_CHAIN_ID,
|
|
"0x000000000000000000000000" + CONTRIBUTED_TOKEN_ONE.address.substr(2),
|
|
tokenOneConversionRate,
|
|
],
|
|
[
|
|
TEST_CHAIN_ID,
|
|
"0x000000000000000000000000" + CONTRIBUTED_TOKEN_TWO.address.substr(2),
|
|
tokenTwoConversionRate,
|
|
],
|
|
];
|
|
|
|
// create the sale
|
|
await initialized.methods.createSale(saleParams, acceptedTokens).send({
|
|
value: "0",
|
|
from: SELLER,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
// Verify Payload sent to contributor
|
|
const log = (
|
|
await WORMHOLE.getPastEvents("LogMessagePublished", {
|
|
fromBlock: "latest",
|
|
})
|
|
)[0].returnValues;
|
|
|
|
// verify payload
|
|
assert.equal(log.sender, TokenSaleConductor.address);
|
|
|
|
// payload id
|
|
let index = 2;
|
|
assert.equal(log.payload.substr(index, 2), payloadIdType1);
|
|
index += 2;
|
|
|
|
// sale id
|
|
SALE_4_ID = SALE_3_ID + 1;
|
|
assert.equal(parseInt(log.payload.substr(index, 64), 16), SALE_4_ID);
|
|
index += 64;
|
|
|
|
// token address
|
|
assert.equal(
|
|
log.payload.substr(index, 64),
|
|
web3.eth.abi.encodeParameter("address", SOLD_TOKEN.address).substring(2)
|
|
);
|
|
index += 64;
|
|
|
|
// token chain
|
|
assert.equal(
|
|
log.payload.substr(index, 4),
|
|
web3.eth.abi
|
|
.encodeParameter("uint16", TEST_CHAIN_ID)
|
|
.substring(2 + 64 - 4)
|
|
);
|
|
index += 4;
|
|
|
|
// token decimals
|
|
assert.equal(
|
|
parseInt(log.payload.substr(index, 2), 16),
|
|
SOLD_TOKEN_DECIMALS
|
|
);
|
|
index += 2;
|
|
|
|
// token amount
|
|
assert.equal(
|
|
parseInt(log.payload.substr(index, 64), 16),
|
|
parseInt(saleTokenAmount)
|
|
);
|
|
index += 64;
|
|
|
|
// min raise amount
|
|
assert.equal(
|
|
parseInt(log.payload.substr(index, 64), 16),
|
|
parseInt(minimumTokenRaise)
|
|
);
|
|
index += 64;
|
|
|
|
// max raise amount
|
|
assert.equal(
|
|
parseInt(log.payload.substr(index, 64), 16),
|
|
parseInt(maximumTokenRaise)
|
|
);
|
|
index += 64;
|
|
|
|
// timestamp start
|
|
assert.equal(parseInt(log.payload.substr(index, 64), 16), SALE_4_START);
|
|
index += 64;
|
|
|
|
// timestamp end
|
|
assert.equal(parseInt(log.payload.substr(index, 64), 16), SALE_4_END);
|
|
index += 64;
|
|
|
|
// accepted tokens length
|
|
assert.equal(
|
|
parseInt(log.payload.substr(index, 2), 16),
|
|
acceptedTokenLength
|
|
);
|
|
index += 2;
|
|
|
|
// token address
|
|
assert.equal(
|
|
log.payload.substr(index, 64),
|
|
web3.eth.abi
|
|
.encodeParameter("address", CONTRIBUTED_TOKEN_ONE.address)
|
|
.substring(2)
|
|
);
|
|
index += 64;
|
|
|
|
// token chain
|
|
assert.equal(
|
|
log.payload.substr(index, 4),
|
|
web3.eth.abi
|
|
.encodeParameter("uint16", TEST_CHAIN_ID)
|
|
.substring(2 + 64 - 4)
|
|
);
|
|
index += 4;
|
|
|
|
// conversion rate
|
|
assert.equal(
|
|
parseInt(log.payload.substr(index, 32), 16),
|
|
parseInt(tokenOneConversionRate)
|
|
);
|
|
index += 32;
|
|
|
|
// token address
|
|
assert.equal(
|
|
log.payload.substr(index, 64),
|
|
web3.eth.abi
|
|
.encodeParameter("address", CONTRIBUTED_TOKEN_TWO.address)
|
|
.substring(2)
|
|
);
|
|
index += 64;
|
|
|
|
// token chain
|
|
assert.equal(
|
|
log.payload.substr(index, 4),
|
|
web3.eth.abi
|
|
.encodeParameter("uint16", TEST_CHAIN_ID)
|
|
.substring(2 + 64 - 4)
|
|
);
|
|
index += 4;
|
|
|
|
// conversion rate
|
|
assert.equal(
|
|
parseInt(log.payload.substr(index, 32), 16),
|
|
parseInt(tokenTwoConversionRate)
|
|
);
|
|
index += 32;
|
|
|
|
// recipient of proceeds
|
|
assert.equal(
|
|
log.payload.substr(index, 64),
|
|
web3.eth.abi.encodeParameter("address", saleRecipient).substring(2)
|
|
);
|
|
index += 64;
|
|
|
|
// refund recipient in case the sale is aborted
|
|
assert.equal(
|
|
log.payload.substr(index, 64),
|
|
web3.eth.abi.encodeParameter("address", refundRecipient).substring(2)
|
|
);
|
|
index += 64;
|
|
|
|
assert.equal(log.payload.length, index);
|
|
SALE_4_INIT_PAYLOAD = log.payload.toString();
|
|
|
|
// verify that getNextSaleId is correct
|
|
const nextSaleId = await initialized.methods.getNextSaleId().call();
|
|
|
|
assert.equal(nextSaleId, SALE_4_ID + 1);
|
|
|
|
// confirm that the localTokenAddress was saved correctl
|
|
const sale = await initialized.methods.sales(SALE_4_ID).call();
|
|
|
|
assert.equal(SOLD_TOKEN.address, sale.localTokenAddress);
|
|
});
|
|
|
|
it("should init a fourth sale in the contributor", async function() {
|
|
// test variables
|
|
const saleTokenAmount = "1000";
|
|
const minimumTokenRaise = "2000";
|
|
const maximumTokenRaise = "6000";
|
|
const tokenOneConversionRate = "1000000000000000000";
|
|
const tokenTwoConversionRate = "4000000000000000000";
|
|
const saleRecipient = accounts[0];
|
|
const refundRecipient = accounts[0];
|
|
|
|
const initialized = new web3.eth.Contract(
|
|
ContributorImplementationFullABI,
|
|
TokenSaleContributor.address
|
|
);
|
|
|
|
const vm = await signAndEncodeVM(
|
|
1,
|
|
1,
|
|
TEST_CHAIN_ID,
|
|
"0x000000000000000000000000" + TokenSaleConductor.address.substr(2),
|
|
0,
|
|
SALE_4_INIT_PAYLOAD,
|
|
[testSigner1PK],
|
|
0,
|
|
0
|
|
);
|
|
|
|
// initialize the fourth sale
|
|
await initialized.methods.initSale("0x" + vm).send({
|
|
from: SELLER,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
// verify sale getter
|
|
const sale = await initialized.methods.sales(SALE_4_ID).call();
|
|
|
|
assert.equal(sale.saleID, SALE_4_ID);
|
|
assert.equal(
|
|
sale.tokenAddress.substring(2),
|
|
web3.eth.abi.encodeParameter("address", SOLD_TOKEN.address).substring(2)
|
|
);
|
|
assert.equal(sale.tokenChain, TEST_CHAIN_ID);
|
|
assert.equal(sale.tokenAmount, saleTokenAmount);
|
|
assert.equal(sale.minRaise, minimumTokenRaise);
|
|
assert.equal(sale.maxRaise, parseInt(maximumTokenRaise));
|
|
assert.equal(sale.saleStart, SALE_4_START);
|
|
assert.equal(sale.saleEnd, SALE_4_END);
|
|
assert.equal(
|
|
sale.acceptedTokensAddresses[TOKEN_ONE_INDEX].substring(2),
|
|
web3.eth.abi
|
|
.encodeParameter("address", CONTRIBUTED_TOKEN_ONE.address)
|
|
.substring(2)
|
|
);
|
|
assert.equal(sale.acceptedTokensChains[TOKEN_ONE_INDEX], TEST_CHAIN_ID);
|
|
assert.equal(
|
|
sale.acceptedTokensConversionRates[TOKEN_ONE_INDEX],
|
|
tokenOneConversionRate
|
|
);
|
|
assert.equal(
|
|
sale.acceptedTokensAddresses[TOKEN_TWO_INDEX].substring(2),
|
|
web3.eth.abi
|
|
.encodeParameter("address", CONTRIBUTED_TOKEN_TWO.address)
|
|
.substring(2)
|
|
);
|
|
assert.equal(sale.acceptedTokensChains[TOKEN_TWO_INDEX], TEST_CHAIN_ID);
|
|
assert.equal(
|
|
sale.acceptedTokensConversionRates[TOKEN_TWO_INDEX],
|
|
tokenTwoConversionRate
|
|
);
|
|
assert.equal(
|
|
sale.recipient.substring(2),
|
|
web3.eth.abi.encodeParameter("address", saleRecipient).substring(2)
|
|
);
|
|
assert.equal(
|
|
sale.refundRecipient.substring(2),
|
|
web3.eth.abi.encodeParameter("address", refundRecipient).substring(2)
|
|
);
|
|
assert.equal(sale.allocations[TOKEN_ONE_INDEX], 0);
|
|
assert.equal(sale.allocations[TOKEN_TWO_INDEX], 0);
|
|
assert.equal(sale.excessContributions[TOKEN_ONE_INDEX], 0);
|
|
assert.equal(sale.excessContributions[TOKEN_TWO_INDEX], 0);
|
|
assert.ok(!sale.isSealed);
|
|
assert.ok(!sale.isAborted);
|
|
|
|
// verify getsaleAcceptedTokenInfo getter
|
|
const tokenOneInfo = await initialized.methods
|
|
.getSaleAcceptedTokenInfo(SALE_4_ID, TOKEN_ONE_INDEX)
|
|
.call();
|
|
const tokenTwoInfo = await initialized.methods
|
|
.getSaleAcceptedTokenInfo(SALE_4_ID, TOKEN_TWO_INDEX)
|
|
.call();
|
|
|
|
assert.equal(
|
|
tokenOneInfo.tokenAddress.substring(2),
|
|
web3.eth.abi
|
|
.encodeParameter("address", CONTRIBUTED_TOKEN_ONE.address)
|
|
.substring(2)
|
|
);
|
|
assert.equal(tokenOneInfo.tokenChainId, TEST_CHAIN_ID);
|
|
assert.equal(tokenOneInfo.conversionRate, tokenOneConversionRate);
|
|
assert.equal(
|
|
tokenTwoInfo.tokenAddress.substring(2),
|
|
web3.eth.abi
|
|
.encodeParameter("address", CONTRIBUTED_TOKEN_TWO.address)
|
|
.substring(2)
|
|
);
|
|
assert.equal(tokenTwoInfo.tokenChainId, TEST_CHAIN_ID);
|
|
assert.equal(tokenTwoInfo.conversionRate, tokenTwoConversionRate);
|
|
|
|
// verify getSaleTimeFrame getter
|
|
const saleTimeframe = await initialized.methods
|
|
.getSaleTimeframe(SALE_4_ID)
|
|
.call();
|
|
|
|
assert.equal(saleTimeframe.start, SALE_4_START);
|
|
assert.equal(saleTimeframe.end, SALE_4_END);
|
|
|
|
// verify getSaleStatus getter
|
|
const saleStatus = await initialized.methods
|
|
.getSaleStatus(SALE_4_ID)
|
|
.call();
|
|
|
|
assert.ok(!saleStatus.isSealed);
|
|
assert.ok(!saleStatus.isAborted);
|
|
});
|
|
|
|
it("should accept contributions in the contributor during the fourth sale timeframe", async function() {
|
|
await wait(5);
|
|
|
|
// test variables
|
|
const tokenOneContributionAmount = ["2000", "4000"];
|
|
const tokenTwoContributionAmount = ["500", "500"];
|
|
|
|
const initialized = new web3.eth.Contract(
|
|
ContributorImplementationFullABI,
|
|
TokenSaleContributor.address
|
|
);
|
|
|
|
// approve contribution amounts
|
|
await CONTRIBUTED_TOKEN_ONE.approve(
|
|
TokenSaleContributor.address,
|
|
parseInt(tokenOneContributionAmount[0]) +
|
|
parseInt(tokenOneContributionAmount[1]),
|
|
{
|
|
from: BUYER_ONE,
|
|
}
|
|
);
|
|
await CONTRIBUTED_TOKEN_TWO.approve(
|
|
TokenSaleContributor.address,
|
|
tokenTwoContributionAmount[0],
|
|
{
|
|
from: BUYER_TWO,
|
|
}
|
|
);
|
|
await CONTRIBUTED_TOKEN_TWO.approve(
|
|
TokenSaleContributor.address,
|
|
tokenTwoContributionAmount[1],
|
|
{
|
|
from: BUYER_ONE,
|
|
}
|
|
);
|
|
|
|
// perform "kyc" and contribute tokens to the sale for BUYER_ONE
|
|
const kycSig1 = await signContribution(
|
|
CONDUCTOR_BYTES32_ADDRESS,
|
|
SALE_4_ID,
|
|
TOKEN_ONE_INDEX,
|
|
tokenOneContributionAmount[0],
|
|
BUYER_ONE,
|
|
kycSignerPK
|
|
);
|
|
await initialized.methods
|
|
.contribute(
|
|
SALE_4_ID,
|
|
TOKEN_ONE_INDEX,
|
|
parseInt(tokenOneContributionAmount[0]),
|
|
kycSig1
|
|
)
|
|
.send({
|
|
from: BUYER_ONE,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
const kycSig2 = await signContribution(
|
|
CONDUCTOR_BYTES32_ADDRESS,
|
|
SALE_4_ID,
|
|
TOKEN_ONE_INDEX,
|
|
tokenOneContributionAmount[1],
|
|
BUYER_ONE,
|
|
kycSignerPK
|
|
);
|
|
await initialized.methods
|
|
.contribute(
|
|
SALE_4_ID,
|
|
TOKEN_ONE_INDEX,
|
|
parseInt(tokenOneContributionAmount[1]),
|
|
kycSig2
|
|
)
|
|
.send({
|
|
from: BUYER_ONE,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
const kycSig3 = await signContribution(
|
|
CONDUCTOR_BYTES32_ADDRESS,
|
|
SALE_4_ID,
|
|
TOKEN_TWO_INDEX,
|
|
tokenTwoContributionAmount[1],
|
|
BUYER_ONE,
|
|
kycSignerPK
|
|
);
|
|
await initialized.methods
|
|
.contribute(
|
|
SALE_4_ID,
|
|
TOKEN_TWO_INDEX,
|
|
parseInt(tokenTwoContributionAmount[1]),
|
|
kycSig3
|
|
)
|
|
.send({
|
|
from: BUYER_ONE,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
// perform "kyc" and contribute tokens to the sale for BUYER_TWO
|
|
const kycSig4 = await signContribution(
|
|
CONDUCTOR_BYTES32_ADDRESS,
|
|
SALE_4_ID,
|
|
TOKEN_TWO_INDEX,
|
|
tokenTwoContributionAmount[0],
|
|
BUYER_TWO,
|
|
kycSignerPK
|
|
);
|
|
await initialized.methods
|
|
.contribute(
|
|
SALE_4_ID,
|
|
TOKEN_TWO_INDEX,
|
|
parseInt(tokenTwoContributionAmount[0]),
|
|
kycSig4
|
|
)
|
|
.send({
|
|
from: BUYER_TWO,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
// verify getSaleTotalContribution after contributing
|
|
const totalContributionsTokenOne = await initialized.methods
|
|
.getSaleTotalContribution(SALE_4_ID, TOKEN_ONE_INDEX)
|
|
.call();
|
|
const totalContributionsTokenTwo = await initialized.methods
|
|
.getSaleTotalContribution(SALE_4_ID, TOKEN_TWO_INDEX)
|
|
.call();
|
|
|
|
assert.equal(
|
|
totalContributionsTokenOne,
|
|
parseInt(tokenOneContributionAmount[0]) +
|
|
parseInt(tokenOneContributionAmount[1])
|
|
);
|
|
assert.equal(
|
|
totalContributionsTokenTwo,
|
|
parseInt(tokenTwoContributionAmount[0]) +
|
|
parseInt(tokenTwoContributionAmount[1])
|
|
);
|
|
|
|
// verify getSaleContribution
|
|
const buyerOneContributionTokenOne = await initialized.methods
|
|
.getSaleContribution(SALE_4_ID, TOKEN_ONE_INDEX, BUYER_ONE)
|
|
.call();
|
|
const buyerOneContributionTokenTwo = await initialized.methods
|
|
.getSaleContribution(SALE_4_ID, TOKEN_TWO_INDEX, BUYER_ONE)
|
|
.call();
|
|
const buyerTwoContribution = await initialized.methods
|
|
.getSaleContribution(SALE_4_ID, TOKEN_TWO_INDEX, BUYER_TWO)
|
|
.call();
|
|
|
|
assert.equal(
|
|
buyerOneContributionTokenOne,
|
|
parseInt(tokenOneContributionAmount[0]) +
|
|
parseInt(tokenOneContributionAmount[1])
|
|
);
|
|
assert.equal(
|
|
buyerOneContributionTokenTwo,
|
|
parseInt(tokenTwoContributionAmount[1])
|
|
);
|
|
assert.equal(buyerTwoContribution, parseInt(tokenTwoContributionAmount[0]));
|
|
});
|
|
|
|
let CONTRIBUTIONS_PAYLOAD_4;
|
|
|
|
it("should attest contributions for fourth sale correctly", async function() {
|
|
await wait(10);
|
|
|
|
// test variables
|
|
const tokenOneContributionAmount = "6000"; // sum of both contributions
|
|
const tokenTwoContributionAmount = "1000";
|
|
const acceptedTokenLength = "2";
|
|
const payloadIdType2 = "02";
|
|
|
|
const initialized = new web3.eth.Contract(
|
|
ContributorImplementationFullABI,
|
|
TokenSaleContributor.address
|
|
);
|
|
|
|
// attest contributions
|
|
await initialized.methods.attestContributions(SALE_4_ID).send({
|
|
from: BUYER_ONE,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
const log = (
|
|
await WORMHOLE.getPastEvents("LogMessagePublished", {
|
|
fromBlock: "latest",
|
|
})
|
|
)[0].returnValues;
|
|
|
|
assert.equal(log.sender, TokenSaleContributor.address);
|
|
|
|
// payload id
|
|
let index = 2;
|
|
assert.equal(log.payload.substr(index, 2), payloadIdType2);
|
|
index += 2;
|
|
|
|
// sale id
|
|
assert.equal(parseInt(log.payload.substr(index, 64), 16), SALE_4_ID);
|
|
index += 64;
|
|
|
|
// chain id
|
|
assert.equal(
|
|
log.payload.substr(index, 4),
|
|
web3.eth.abi.encodeParameter("uint16", 2).substring(2 + 64 - 4)
|
|
);
|
|
index += 4;
|
|
|
|
// tokens length
|
|
assert.equal(
|
|
parseInt(log.payload.substr(index, 2), 16),
|
|
acceptedTokenLength
|
|
);
|
|
index += 2;
|
|
|
|
// token index
|
|
assert.equal(
|
|
log.payload.substr(index, 2),
|
|
web3.eth.abi.encodeParameter("uint8", 0).substring(2 + 64 - 2)
|
|
);
|
|
index += 2;
|
|
|
|
// amount
|
|
assert.equal(
|
|
parseInt(log.payload.substr(index, 64), 16),
|
|
tokenOneContributionAmount
|
|
);
|
|
index += 64;
|
|
|
|
// token index
|
|
assert.equal(
|
|
log.payload.substr(index, 2),
|
|
web3.eth.abi.encodeParameter("uint8", 1).substring(2 + 64 - 2)
|
|
);
|
|
index += 2;
|
|
|
|
// amount
|
|
assert.equal(
|
|
parseInt(log.payload.substr(index, 64), 16),
|
|
tokenTwoContributionAmount
|
|
);
|
|
index += 64;
|
|
|
|
assert.equal(log.payload.length, index);
|
|
|
|
CONTRIBUTIONS_PAYLOAD_4 = log.payload.toString();
|
|
});
|
|
|
|
it("conductor should collect fourth sale contributions correctly", async function() {
|
|
// test variables
|
|
const tokenOneContributionAmount = "6000"; // both contributions
|
|
const tokenTwoContributionAmount = "1000";
|
|
|
|
const initialized = new web3.eth.Contract(
|
|
ConductorImplementationFullABI,
|
|
TokenSaleConductor.address
|
|
);
|
|
|
|
// verify saleContributionIsCollected getter before collecting contributions
|
|
const isContributionOneCollectedBefore = await initialized.methods
|
|
.saleContributionIsCollected(SALE_4_ID, TOKEN_ONE_INDEX)
|
|
.call();
|
|
const isContributionTwoCollectedBefore = await initialized.methods
|
|
.saleContributionIsCollected(SALE_4_ID, TOKEN_TWO_INDEX)
|
|
.call();
|
|
|
|
assert.ok(!isContributionOneCollectedBefore);
|
|
assert.ok(!isContributionTwoCollectedBefore);
|
|
|
|
const vm = await signAndEncodeVM(
|
|
1,
|
|
1,
|
|
TEST_CHAIN_ID,
|
|
"0x000000000000000000000000" + TokenSaleContributor.address.substr(2),
|
|
0,
|
|
CONTRIBUTIONS_PAYLOAD_4,
|
|
[testSigner1PK],
|
|
0,
|
|
0
|
|
);
|
|
|
|
await initialized.methods.collectContribution("0x" + vm).send({
|
|
from: SELLER,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
// verify saleContributionIsCollected getter after collecting contributions
|
|
const isContributionOneCollectedAfter = await initialized.methods
|
|
.saleContributionIsCollected(SALE_4_ID, TOKEN_ONE_INDEX)
|
|
.call();
|
|
const isContributionTwoCollectedAfter = await initialized.methods
|
|
.saleContributionIsCollected(SALE_4_ID, TOKEN_TWO_INDEX)
|
|
.call();
|
|
|
|
assert.ok(isContributionOneCollectedAfter);
|
|
assert.ok(isContributionTwoCollectedAfter);
|
|
|
|
// verify saleContributions getter
|
|
const contributions = await initialized.methods
|
|
.saleContributions(SALE_4_ID)
|
|
.call();
|
|
|
|
assert.equal(contributions[0], tokenOneContributionAmount);
|
|
assert.equal(contributions[1], tokenTwoContributionAmount);
|
|
});
|
|
|
|
let SALE_SEALED_PAYLOAD_4;
|
|
|
|
it("conductor should seal the fourth sale correctly and distribute tokens", async function() {
|
|
// test variables
|
|
const expectedContributorBalanceBefore = "600";
|
|
const expectedConductorBalanceBefore = "1000";
|
|
const expectedContributorBalanceAfter = "1600";
|
|
const expectedConductorBalanceAfter = "0";
|
|
const payloadIdType3 = "03";
|
|
|
|
const initialized = new web3.eth.Contract(
|
|
ConductorImplementationFullABI,
|
|
TokenSaleConductor.address
|
|
);
|
|
|
|
const actualContributorBalanceBefore = await SOLD_TOKEN.balanceOf(
|
|
TokenSaleContributor.address
|
|
);
|
|
const actualConductorBalanceBefore = await SOLD_TOKEN.balanceOf(
|
|
TokenSaleConductor.address
|
|
);
|
|
|
|
assert.equal(
|
|
actualContributorBalanceBefore,
|
|
expectedContributorBalanceBefore
|
|
);
|
|
assert.equal(actualConductorBalanceBefore, expectedConductorBalanceBefore);
|
|
|
|
// verify sealSealed flag in sales
|
|
const saleBefore = await initialized.methods.sales(SALE_4_ID).call();
|
|
|
|
assert.ok(!saleBefore.isSealed);
|
|
|
|
// seal the sale
|
|
await initialized.methods.sealSale(SALE_4_ID).send({
|
|
from: SELLER,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
const actualContributorBalanceAfter = await SOLD_TOKEN.balanceOf(
|
|
TokenSaleContributor.address
|
|
);
|
|
const actualConductorBalanceAfter = await SOLD_TOKEN.balanceOf(
|
|
TokenSaleConductor.address
|
|
);
|
|
|
|
assert.equal(
|
|
actualContributorBalanceAfter,
|
|
expectedContributorBalanceAfter
|
|
);
|
|
assert.equal(actualConductorBalanceAfter, expectedConductorBalanceAfter);
|
|
|
|
const log = (
|
|
await WORMHOLE.getPastEvents("LogMessagePublished", {
|
|
fromBlock: "latest",
|
|
})
|
|
)[0].returnValues;
|
|
|
|
// verify saleSealed payload
|
|
assert.equal(log.sender, TokenSaleConductor.address);
|
|
|
|
// payload id
|
|
let index = 2;
|
|
assert.equal(log.payload.substr(index, 2), payloadIdType3);
|
|
index += 2;
|
|
|
|
// sale id
|
|
assert.equal(parseInt(log.payload.substr(index, 64), 16), SALE_4_ID);
|
|
index += 64;
|
|
|
|
SALE_SEALED_PAYLOAD_4 = log.payload;
|
|
|
|
// verify saleSealed flag after sealing the sale
|
|
const saleAfter = await initialized.methods.sales(SALE_4_ID).call();
|
|
|
|
assert.ok(saleAfter.isSealed);
|
|
});
|
|
|
|
it("contributor should seal the fourth sale correctly", async function() {
|
|
// test variables
|
|
const expectedAllocationTokenOne = "600";
|
|
const expectedAllocationTokenTwo = "400";
|
|
const expectedExcessTokenOne = "2400";
|
|
const expectedExcessTokenTwo = "400";
|
|
const expectedRecipientTokenOneBalanceChange = "3600";
|
|
const expectedRecipientTokenTwoBalanceChange = "600";
|
|
|
|
const initialized = new web3.eth.Contract(
|
|
ContributorImplementationFullABI,
|
|
TokenSaleContributor.address
|
|
);
|
|
|
|
// grab contributed token balance before for sale recipient
|
|
const receipientTokenOneBalanceBefore = await CONTRIBUTED_TOKEN_ONE.balanceOf(
|
|
SELLER
|
|
);
|
|
const receipientTokenTwoBalanceBefore = await CONTRIBUTED_TOKEN_TWO.balanceOf(
|
|
SELLER
|
|
);
|
|
|
|
// verify sealSealed getters before calling saleSealed
|
|
const saleBefore = await initialized.methods.sales(SALE_4_ID).call();
|
|
|
|
// verify isSealed flag before
|
|
assert.ok(!saleBefore.isSealed);
|
|
|
|
const vm = await signAndEncodeVM(
|
|
1,
|
|
1,
|
|
TEST_CHAIN_ID,
|
|
"0x000000000000000000000000" + TokenSaleConductor.address.substr(2),
|
|
0,
|
|
SALE_SEALED_PAYLOAD_4,
|
|
[testSigner1PK],
|
|
0,
|
|
0
|
|
);
|
|
|
|
// seal the sale
|
|
await initialized.methods.saleSealed("0x" + vm).send({
|
|
from: BUYER_ONE,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
// confirm that the sale was sealed
|
|
const saleAfter = await initialized.methods.sales(SALE_4_ID).call();
|
|
|
|
assert.ok(saleAfter.isSealed);
|
|
|
|
// verify getSaleAllocation after sealing the sale
|
|
const actualAllocationTokenOne = await initialized.methods
|
|
.getSaleAllocation(SALE_4_ID, TOKEN_ONE_INDEX)
|
|
.call();
|
|
const actualAllocationTokenTwo = await initialized.methods
|
|
.getSaleAllocation(SALE_4_ID, TOKEN_TWO_INDEX)
|
|
.call();
|
|
|
|
assert.equal(actualAllocationTokenOne, expectedAllocationTokenOne);
|
|
assert.equal(actualAllocationTokenTwo, expectedAllocationTokenTwo);
|
|
|
|
// verify getSaleExcessContribution after sealing the sale
|
|
const actualExcessTokenOne = await initialized.methods
|
|
.getSaleExcessContribution(SALE_4_ID, TOKEN_ONE_INDEX)
|
|
.call();
|
|
const actualExcessTokenTwo = await initialized.methods
|
|
.getSaleExcessContribution(SALE_4_ID, TOKEN_TWO_INDEX)
|
|
.call();
|
|
|
|
assert.equal(actualExcessTokenOne, expectedExcessTokenOne);
|
|
assert.equal(actualExcessTokenTwo, expectedExcessTokenTwo);
|
|
|
|
// confirm that the sale recipient recieved the correct amount of contributions
|
|
const receipientTokenOneBalanceAfter = await CONTRIBUTED_TOKEN_ONE.balanceOf(
|
|
SELLER
|
|
);
|
|
const receipientTokenTwoBalanceAfter = await CONTRIBUTED_TOKEN_TWO.balanceOf(
|
|
SELLER
|
|
);
|
|
|
|
assert.equal(
|
|
receipientTokenOneBalanceAfter - receipientTokenOneBalanceBefore,
|
|
expectedRecipientTokenOneBalanceChange
|
|
);
|
|
assert.equal(
|
|
receipientTokenTwoBalanceAfter - receipientTokenTwoBalanceBefore,
|
|
expectedRecipientTokenTwoBalanceChange
|
|
);
|
|
});
|
|
|
|
it("contributor should distribute tokens correctly and excess contributions correctly", async function() {
|
|
// test variables
|
|
const expectedContributorSaleTokenBalanceChange = "1000";
|
|
const expectedBuyerOneSaleTokenBalanceChange = "800"; // 80% of total contribution (2000 * 1 + 4000 * 1 + 500 * 4 = 8000)
|
|
const expectedBuyerTwoSaleTokenBalanceChange = "200"; // 20% of total contribution (500 * 4 = 2000)
|
|
|
|
// expected refunds from excess contributions
|
|
// 10k contributions - 6k maxRaise = 4k in refunds (multiplier applied)
|
|
const expectedBuyerOneTokenOneRefund = "2400"; // .6 * 4k / 1 (multiplier)
|
|
const expectedBuyerOneTokenTwoRefund = "200"; // .2 * 4k / 4 (multiplier)
|
|
const expectedBuyerTwoTokenTwoRefund = "200"; // .2 * 4k / 4 (multiplier)
|
|
|
|
const initialized = new web3.eth.Contract(
|
|
ContributorImplementationFullABI,
|
|
TokenSaleContributor.address
|
|
);
|
|
|
|
// check balances before claiming allocations and excess contributions
|
|
const contributorSaleTokenBalanceBefore = await SOLD_TOKEN.balanceOf(
|
|
TokenSaleContributor.address
|
|
);
|
|
const buyerOneSaleTokenBalanceBefore = await SOLD_TOKEN.balanceOf(
|
|
BUYER_ONE
|
|
);
|
|
const buyerTwoSaleTokenBalanceBefore = await SOLD_TOKEN.balanceOf(
|
|
BUYER_TWO
|
|
);
|
|
|
|
const buyerOneTokenOneBalanceBefore = await CONTRIBUTED_TOKEN_ONE.balanceOf(
|
|
BUYER_ONE
|
|
);
|
|
const buyerOneTokenTwoBalanceBefore = await CONTRIBUTED_TOKEN_TWO.balanceOf(
|
|
BUYER_ONE
|
|
);
|
|
const buyerTwoTokenTwoBalanceBefore = await CONTRIBUTED_TOKEN_TWO.balanceOf(
|
|
BUYER_TWO
|
|
);
|
|
|
|
// verify allocationIsClaimed before claiming allocation
|
|
const isAllocationClaimedBuyerOneTokenOneBefore = await initialized.methods
|
|
.allocationIsClaimed(SALE_4_ID, TOKEN_ONE_INDEX, BUYER_ONE)
|
|
.call();
|
|
const isAllocationClaimedBuyerOneTokenTwoBefore = await initialized.methods
|
|
.allocationIsClaimed(SALE_4_ID, TOKEN_TWO_INDEX, BUYER_ONE)
|
|
.call();
|
|
const isAllocationClaimedBuyerTwoBefore = await initialized.methods
|
|
.allocationIsClaimed(SALE_4_ID, TOKEN_TWO_INDEX, BUYER_TWO)
|
|
.call();
|
|
|
|
assert.ok(!isAllocationClaimedBuyerOneTokenOneBefore);
|
|
assert.ok(!isAllocationClaimedBuyerOneTokenTwoBefore);
|
|
assert.ok(!isAllocationClaimedBuyerTwoBefore);
|
|
|
|
// claim allocations for both tokens
|
|
await initialized.methods.claimAllocation(SALE_4_ID, TOKEN_ONE_INDEX).send({
|
|
from: BUYER_ONE,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
await initialized.methods.claimAllocation(SALE_4_ID, TOKEN_TWO_INDEX).send({
|
|
from: BUYER_ONE,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
await initialized.methods.claimAllocation(SALE_4_ID, TOKEN_TWO_INDEX).send({
|
|
from: BUYER_TWO,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
// check that sale token allocations were distributed correctly
|
|
const contributorSaleTokenBalanceAfter = await SOLD_TOKEN.balanceOf(
|
|
TokenSaleContributor.address
|
|
);
|
|
const buyerOneSaleTokenBalanceAfter = await SOLD_TOKEN.balanceOf(BUYER_ONE);
|
|
const buyerTwoSaleTokenBalanceAfter = await SOLD_TOKEN.balanceOf(BUYER_TWO);
|
|
|
|
assert.equal(
|
|
contributorSaleTokenBalanceBefore - contributorSaleTokenBalanceAfter,
|
|
expectedContributorSaleTokenBalanceChange
|
|
);
|
|
assert.equal(
|
|
buyerOneSaleTokenBalanceAfter - buyerOneSaleTokenBalanceBefore,
|
|
expectedBuyerOneSaleTokenBalanceChange
|
|
);
|
|
assert.equal(
|
|
buyerTwoSaleTokenBalanceAfter - buyerTwoSaleTokenBalanceBefore,
|
|
expectedBuyerTwoSaleTokenBalanceChange
|
|
);
|
|
|
|
// check that excess contributions were distributed correctly
|
|
const buyerOneTokenOneBalanceAfter = await CONTRIBUTED_TOKEN_ONE.balanceOf(
|
|
BUYER_ONE
|
|
);
|
|
const buyerOneTokenTwoBalanceAfter = await CONTRIBUTED_TOKEN_TWO.balanceOf(
|
|
BUYER_ONE
|
|
);
|
|
const buyerTwoTokenTwoBalanceAfter = await CONTRIBUTED_TOKEN_TWO.balanceOf(
|
|
BUYER_TWO
|
|
);
|
|
|
|
assert.equal(
|
|
buyerOneTokenOneBalanceAfter - buyerOneTokenOneBalanceBefore,
|
|
expectedBuyerOneTokenOneRefund
|
|
);
|
|
assert.equal(
|
|
buyerOneTokenTwoBalanceAfter - buyerOneTokenTwoBalanceBefore,
|
|
expectedBuyerOneTokenTwoRefund
|
|
);
|
|
assert.equal(
|
|
buyerTwoTokenTwoBalanceAfter - buyerTwoTokenTwoBalanceBefore,
|
|
expectedBuyerTwoTokenTwoRefund
|
|
);
|
|
|
|
// verify allocationIsClaimed before claiming allocation
|
|
const isAllocationClaimedBuyerOneTokenOneAfter = await initialized.methods
|
|
.allocationIsClaimed(SALE_4_ID, TOKEN_ONE_INDEX, BUYER_ONE)
|
|
.call();
|
|
const isAllocationClaimedBuyerOneTokenTwoAfter = await initialized.methods
|
|
.allocationIsClaimed(SALE_4_ID, TOKEN_TWO_INDEX, BUYER_ONE)
|
|
.call();
|
|
const isAllocationClaimedBuyerTwoAfter = await initialized.methods
|
|
.allocationIsClaimed(SALE_4_ID, TOKEN_TWO_INDEX, BUYER_TWO)
|
|
.call();
|
|
|
|
assert.ok(isAllocationClaimedBuyerOneTokenOneAfter);
|
|
assert.ok(isAllocationClaimedBuyerOneTokenTwoAfter);
|
|
assert.ok(isAllocationClaimedBuyerTwoAfter);
|
|
});
|
|
|
|
it("conductor should not allow a sale to abort after the sale start time", async function() {
|
|
console.log(
|
|
"\n -------------------------- Other Tests --------------------------"
|
|
);
|
|
// test variables
|
|
const current_block = await web3.eth.getBlock("latest");
|
|
const saleStart = current_block.timestamp + 5;
|
|
const saleEnd = saleStart + 8;
|
|
const saleTokenAmount = "1000";
|
|
const minimumTokenRaise = "2000";
|
|
const maximumTokenRaise = "30000";
|
|
const tokenOneConversionRate = "1000000000000000000";
|
|
const tokenTwoConversionRate = "2000000000000000000";
|
|
const saleRecipient = accounts[0];
|
|
const refundRecipient = accounts[0];
|
|
const saleId5 = 4;
|
|
|
|
const initialized = new web3.eth.Contract(
|
|
ConductorImplementationFullABI,
|
|
TokenSaleConductor.address
|
|
);
|
|
|
|
await SOLD_TOKEN.approve(TokenSaleConductor.address, saleTokenAmount);
|
|
|
|
// create array (solidity struct) for sale params
|
|
const saleParams = [
|
|
SOLD_TOKEN_BYTES32_ADDRESS,
|
|
TEST_CHAIN_ID,
|
|
saleTokenAmount,
|
|
minimumTokenRaise,
|
|
maximumTokenRaise,
|
|
saleStart,
|
|
saleEnd,
|
|
saleRecipient,
|
|
refundRecipient,
|
|
];
|
|
|
|
// create accepted tokens array
|
|
const acceptedTokens = [
|
|
[
|
|
TEST_CHAIN_ID,
|
|
"0x000000000000000000000000" + CONTRIBUTED_TOKEN_ONE.address.substr(2),
|
|
tokenOneConversionRate,
|
|
],
|
|
[
|
|
TEST_CHAIN_ID,
|
|
"0x000000000000000000000000" + CONTRIBUTED_TOKEN_TWO.address.substr(2),
|
|
tokenTwoConversionRate,
|
|
],
|
|
];
|
|
|
|
// create a another sale
|
|
await initialized.methods.createSale(saleParams, acceptedTokens).send({
|
|
value: "0",
|
|
from: SELLER,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
// wait for the sale to start
|
|
await wait(6);
|
|
|
|
let failed = false;
|
|
try {
|
|
// try to abort abort after the sale started
|
|
await initialized.methods.abortSaleBeforeStartTime(saleId5).send({
|
|
from: SELLER,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
} catch (e) {
|
|
assert.equal(
|
|
e.message,
|
|
"Returned error: VM Exception while processing transaction: revert sale cannot be aborted once it has started"
|
|
);
|
|
failed = true;
|
|
}
|
|
|
|
assert.ok(failed);
|
|
});
|
|
|
|
it("contributor should not initialize a sale with non-ERC20 tokens", async function() {
|
|
// test variables
|
|
const current_block = await web3.eth.getBlock("latest");
|
|
const saleStart = current_block.timestamp + 5;
|
|
const saleEnd = saleStart + 8;
|
|
const saleTokenAmount = "10";
|
|
const minimumTokenRaise = "2000";
|
|
const maximumTokenRaise = "30000";
|
|
const tokenOneConversionRate = "1000000000000000000";
|
|
const tokenTwoConversionRate = "2000000000000000000";
|
|
const saleRecipient = accounts[0];
|
|
const refundRecipient = accounts[0];
|
|
const SOLD_TOKEN_DECIMALS = 18;
|
|
const mintAccount = SELLER;
|
|
const tokenSequence = 0; // set to 0 for the test
|
|
const tokenChainId = 0; // set to 0 for the test
|
|
const nativeContractAddress = "0x00"; // set to 0 for the test
|
|
|
|
const initializedConductor = new web3.eth.Contract(
|
|
ConductorImplementationFullABI,
|
|
TokenSaleConductor.address
|
|
);
|
|
const initializedContributor = new web3.eth.Contract(
|
|
ContributorImplementationFullABI,
|
|
TokenSaleContributor.address
|
|
);
|
|
|
|
// create sale token again
|
|
const saleTokenMintAmount = "2000";
|
|
const soldToken = await TokenImplementation.new();
|
|
const soldTokenName = "Sold Token";
|
|
const soldTokenSymbol = "SOLD";
|
|
|
|
await soldToken.initialize(
|
|
soldTokenName,
|
|
soldTokenSymbol,
|
|
SOLD_TOKEN_DECIMALS,
|
|
tokenSequence,
|
|
mintAccount,
|
|
tokenChainId,
|
|
nativeContractAddress
|
|
);
|
|
await soldToken.mint(SELLER, saleTokenMintAmount);
|
|
await soldToken.approve(TokenSaleConductor.address, saleTokenAmount);
|
|
const soldTokenBytes32 =
|
|
"0x000000000000000000000000" + soldToken.address.substr(2);
|
|
|
|
// create array (solidity struct) for sale params
|
|
const saleParams = [
|
|
soldTokenBytes32,
|
|
TEST_CHAIN_ID,
|
|
saleTokenAmount,
|
|
minimumTokenRaise,
|
|
maximumTokenRaise,
|
|
saleStart,
|
|
saleEnd,
|
|
saleRecipient,
|
|
refundRecipient,
|
|
];
|
|
|
|
// create accepted tokens array
|
|
const acceptedTokens = [
|
|
[
|
|
TEST_CHAIN_ID,
|
|
"0x000000000000000000000000" + CONTRIBUTED_TOKEN_ONE.address.substr(2),
|
|
tokenOneConversionRate,
|
|
],
|
|
[
|
|
TEST_CHAIN_ID,
|
|
"0x000000000000000000000000" + accounts[0].substr(2), // create bad address
|
|
tokenTwoConversionRate,
|
|
],
|
|
];
|
|
|
|
// create a another sale
|
|
await initializedConductor.methods
|
|
.createSale(saleParams, acceptedTokens)
|
|
.send({
|
|
value: "0",
|
|
from: SELLER,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
// Grab the message generated by the conductor
|
|
const log = (
|
|
await WORMHOLE.getPastEvents("LogMessagePublished", {
|
|
fromBlock: "latest",
|
|
})
|
|
)[0].returnValues;
|
|
|
|
// create the vaa to initialize the sale
|
|
const vm = await signAndEncodeVM(
|
|
1,
|
|
1,
|
|
TEST_CHAIN_ID,
|
|
"0x000000000000000000000000" + TokenSaleConductor.address.substr(2),
|
|
0,
|
|
log.payload.toString(),
|
|
[testSigner1PK],
|
|
0,
|
|
0
|
|
);
|
|
|
|
let failed = false;
|
|
try {
|
|
// try to initialize with a bad accepted token address
|
|
await initializedContributor.methods.initSale("0x" + vm).send({
|
|
from: SELLER,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
} catch (e) {
|
|
assert.equal(
|
|
e.message,
|
|
"Returned error: VM Exception while processing transaction: revert non-existent ERC20"
|
|
);
|
|
failed = true;
|
|
}
|
|
|
|
assert.ok(failed);
|
|
});
|
|
|
|
it("conductor should only accept tokens with non-zero conversion rates", async function() {
|
|
// test variables
|
|
const current_block = await web3.eth.getBlock("latest");
|
|
const saleStart = current_block.timestamp + 5;
|
|
const saleEnd = saleStart + 8;
|
|
const saleTokenAmount = "10";
|
|
const minimumTokenRaise = "2000";
|
|
const maximumTokenRaise = "30000";
|
|
const tokenOneConversionRate = "0"; // set to 0 for the test
|
|
const tokenTwoConversionRate = "2000000000000000000";
|
|
const saleRecipient = accounts[0];
|
|
const refundRecipient = accounts[0];
|
|
const SOLD_TOKEN_DECIMALS = 18;
|
|
const mintAccount = SELLER;
|
|
const tokenSequence = 0; // set to 0 for the test
|
|
const tokenChainId = 0; // set to 0 for the test
|
|
const nativeContractAddress = "0x00"; // set to 0 for the test
|
|
|
|
const initialized = new web3.eth.Contract(
|
|
ConductorImplementationFullABI,
|
|
TokenSaleConductor.address
|
|
);
|
|
|
|
// create sale token again
|
|
const saleTokenMintAmount = "2000";
|
|
const soldToken = await TokenImplementation.new();
|
|
const soldTokenName = "Sold Token";
|
|
const soldTokenSymbol = "SOLD";
|
|
const soldTokenBytes32 =
|
|
"0x000000000000000000000000" + soldToken.address.substr(2);
|
|
|
|
await soldToken.initialize(
|
|
soldTokenName,
|
|
soldTokenSymbol,
|
|
SOLD_TOKEN_DECIMALS,
|
|
tokenSequence,
|
|
mintAccount,
|
|
tokenChainId,
|
|
nativeContractAddress
|
|
);
|
|
await soldToken.mint(SELLER, saleTokenMintAmount);
|
|
await soldToken.approve(TokenSaleConductor.address, saleTokenAmount);
|
|
|
|
// create array (solidity struct) for sale params
|
|
const saleParams = [
|
|
soldTokenBytes32,
|
|
TEST_CHAIN_ID,
|
|
saleTokenAmount,
|
|
minimumTokenRaise,
|
|
maximumTokenRaise,
|
|
saleStart,
|
|
saleEnd,
|
|
saleRecipient,
|
|
refundRecipient,
|
|
];
|
|
|
|
// create accepted tokens array
|
|
const acceptedTokens = [
|
|
[
|
|
TEST_CHAIN_ID,
|
|
"0x000000000000000000000000" + CONTRIBUTED_TOKEN_ONE.address.substr(2),
|
|
tokenOneConversionRate,
|
|
],
|
|
[
|
|
TEST_CHAIN_ID,
|
|
"0x000000000000000000000000" + CONTRIBUTED_TOKEN_TWO.address.substr(2), // create bad address
|
|
tokenTwoConversionRate,
|
|
],
|
|
];
|
|
|
|
let failed = false;
|
|
try {
|
|
// try to create a sale with a token with zero multiplier
|
|
await initialized.methods.createSale(saleParams, acceptedTokens).send({
|
|
value: "0",
|
|
from: SELLER,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
} catch (e) {
|
|
assert.equal(
|
|
e.message,
|
|
"Returned error: VM Exception while processing transaction: revert conversion rate cannot be zero"
|
|
);
|
|
failed = true;
|
|
}
|
|
|
|
assert.ok(failed);
|
|
});
|
|
|
|
it("conductor should not accept sale start/end times larger than uint64", async function() {
|
|
// test variables
|
|
const saleStart = "100000000000000000000000";
|
|
const saleEnd = "100000000000000000000001";
|
|
const saleTokenAmount = "10";
|
|
const minimumTokenRaise = "2000";
|
|
const maximumTokenRaise = "30000";
|
|
const tokenOneConversionRate = "1000000000000000000";
|
|
const saleRecipient = accounts[0];
|
|
const refundRecipient = accounts[0];
|
|
const SOLD_TOKEN_DECIMALS = 18;
|
|
const mintAccount = SELLER;
|
|
const tokenSequence = 0; // set to 0 for the test
|
|
const tokenChainId = 0; // set to 0 for the test
|
|
const nativeContractAddress = "0x00"; // set to 0 for the test
|
|
|
|
const initialized = new web3.eth.Contract(
|
|
ConductorImplementationFullABI,
|
|
TokenSaleConductor.address
|
|
);
|
|
|
|
// create sale token again
|
|
const saleTokenMintAmount = "2000";
|
|
const soldToken = await TokenImplementation.new();
|
|
const soldTokenName = "Sold Token";
|
|
const soldTokenSymbol = "SOLD";
|
|
const soldTokenBytes32 =
|
|
"0x000000000000000000000000" + soldToken.address.substr(2);
|
|
|
|
await soldToken.initialize(
|
|
soldTokenName,
|
|
soldTokenSymbol,
|
|
SOLD_TOKEN_DECIMALS,
|
|
tokenSequence,
|
|
mintAccount,
|
|
tokenChainId,
|
|
nativeContractAddress
|
|
);
|
|
await soldToken.mint(SELLER, saleTokenMintAmount);
|
|
await soldToken.approve(TokenSaleConductor.address, saleTokenAmount);
|
|
|
|
// create array (solidity struct) for sale params
|
|
const saleParams = [
|
|
soldTokenBytes32,
|
|
TEST_CHAIN_ID,
|
|
saleTokenAmount,
|
|
minimumTokenRaise,
|
|
maximumTokenRaise,
|
|
saleStart,
|
|
saleEnd,
|
|
saleRecipient,
|
|
refundRecipient,
|
|
];
|
|
|
|
// create accepted tokens array
|
|
const acceptedTokens = [
|
|
[
|
|
TEST_CHAIN_ID,
|
|
"0x000000000000000000000000" + CONTRIBUTED_TOKEN_ONE.address.substr(2),
|
|
tokenOneConversionRate,
|
|
],
|
|
];
|
|
|
|
let failed = false;
|
|
try {
|
|
// try to create a sale with sale start/end times larger than uint64
|
|
await initialized.methods.createSale(saleParams, acceptedTokens).send({
|
|
value: "0",
|
|
from: SELLER,
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
} catch (e) {
|
|
assert.equal(
|
|
e.message,
|
|
"Returned error: VM Exception while processing transaction: revert saleStart too far in the future"
|
|
);
|
|
failed = true;
|
|
}
|
|
|
|
assert.ok(failed);
|
|
});
|
|
});
|
|
|
|
contract("ICCO Library Upgrade", function(accounts) {
|
|
it("conductor should accept a valid upgrade with library changes", async function() {
|
|
const initialized = new web3.eth.Contract(
|
|
ConductorImplementationFullABI,
|
|
TokenSaleConductor.address
|
|
);
|
|
|
|
// deploy mock contracts and link ICCOStructs library
|
|
const structs = await MockICCOStructs.new();
|
|
await MockConductorImplementation2.link(structs, structs.address);
|
|
const mock = await MockConductorImplementation2.new();
|
|
|
|
// confirm that the implementation address changes
|
|
let before = await web3.eth.getStorageAt(
|
|
TokenSaleConductor.address,
|
|
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc"
|
|
);
|
|
|
|
assert.equal(
|
|
before.toLowerCase(),
|
|
ConductorImplementation.address.toLowerCase()
|
|
);
|
|
|
|
await initialized.methods.upgrade(TEST_CHAIN_ID, mock.address).send({
|
|
value: 0,
|
|
from: accounts[0],
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
let after = await web3.eth.getStorageAt(
|
|
TokenSaleConductor.address,
|
|
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc"
|
|
);
|
|
|
|
assert.equal(after.toLowerCase(), mock.address.toLowerCase());
|
|
|
|
// call new conductor methods to confirm the upgrade was successful
|
|
const mockImpl = new web3.eth.Contract(
|
|
MockConductorImplementation2.abi,
|
|
TokenSaleConductor.address
|
|
);
|
|
|
|
let isUpgraded = await mockImpl.methods
|
|
.testNewImplementationActive()
|
|
.call();
|
|
let isConductorUpgraded = await mockImpl.methods.upgradeSuccessful().call();
|
|
|
|
assert.ok(isUpgraded);
|
|
assert.ok(isConductorUpgraded);
|
|
|
|
// call new method in mock ICCO structs to confirm library upgrade was successful
|
|
const mockICCOLib = new web3.eth.Contract(
|
|
MockICCOStructs.abi,
|
|
structs.address
|
|
);
|
|
|
|
let isLibraryUpgraded = await mockICCOLib.methods
|
|
.testNewLibraryActive()
|
|
.call();
|
|
|
|
assert.ok(isLibraryUpgraded);
|
|
});
|
|
|
|
it("contributor should accept a valid upgrade with library changes", async function() {
|
|
const initialized = new web3.eth.Contract(
|
|
ContributorImplementationFullABI,
|
|
TokenSaleContributor.address
|
|
);
|
|
|
|
// deploy mock contracts and link ICCOStructs library
|
|
const structs = await MockICCOStructs.new();
|
|
await MockContributorImplementation2.link(structs, structs.address);
|
|
const mock = await MockContributorImplementation2.new();
|
|
|
|
// confirm that the implementation address changes
|
|
let before = await web3.eth.getStorageAt(
|
|
TokenSaleContributor.address,
|
|
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc"
|
|
);
|
|
|
|
assert.equal(
|
|
before.toLowerCase(),
|
|
ContributorImplementation.address.toLowerCase()
|
|
);
|
|
|
|
await initialized.methods.upgrade(TEST_CHAIN_ID, mock.address).send({
|
|
value: 0,
|
|
from: accounts[0],
|
|
gasLimit: GAS_LIMIT,
|
|
});
|
|
|
|
let after = await web3.eth.getStorageAt(
|
|
TokenSaleContributor.address,
|
|
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc"
|
|
);
|
|
|
|
assert.equal(after.toLowerCase(), mock.address.toLowerCase());
|
|
|
|
// // call new contributor methods to confirm the upgrade was successful
|
|
const mockImpl = new web3.eth.Contract(
|
|
MockContributorImplementation2.abi,
|
|
TokenSaleContributor.address
|
|
);
|
|
|
|
let isUpgraded = await mockImpl.methods
|
|
.testNewImplementationActive()
|
|
.call();
|
|
let isContributorUpgraded = await mockImpl.methods
|
|
.upgradeSuccessful()
|
|
.call();
|
|
|
|
assert.ok(isUpgraded);
|
|
assert.ok(isContributorUpgraded);
|
|
|
|
// call new method in ICCO structs to confirm library upgrade was successful
|
|
const mockICCOLib = new web3.eth.Contract(
|
|
MockICCOStructs.abi,
|
|
structs.address
|
|
);
|
|
|
|
let isLibraryUpgraded = await mockICCOLib.methods
|
|
.testNewLibraryActive()
|
|
.call();
|
|
|
|
assert.ok(isLibraryUpgraded);
|
|
});
|
|
});
|
|
|
|
const signContribution = async function(
|
|
conductorAddress,
|
|
saleId,
|
|
tokenIndex,
|
|
amount,
|
|
buyerAddress,
|
|
signer
|
|
) {
|
|
// query for total contributed amount by this contributor
|
|
const initialized = new web3.eth.Contract(
|
|
ContributorImplementationFullABI,
|
|
TokenSaleContributor.address
|
|
);
|
|
const totalContribution = await initialized.methods
|
|
.getSaleContribution(saleId, tokenIndex, buyerAddress)
|
|
.call();
|
|
|
|
const body = [
|
|
web3.eth.abi.encodeParameter("bytes32", conductorAddress).substring(2),
|
|
web3.eth.abi.encodeParameter("uint256", saleId).substring(2),
|
|
web3.eth.abi.encodeParameter("uint256", tokenIndex).substring(2),
|
|
web3.eth.abi.encodeParameter("uint256", amount).substring(2),
|
|
web3.eth.abi
|
|
.encodeParameter("address", buyerAddress)
|
|
.substring(2 + (64 - 40)),
|
|
web3.eth.abi.encodeParameter("uint256", totalContribution).substring(2),
|
|
];
|
|
|
|
// compute the hash
|
|
const hash = web3.utils.soliditySha3("0x" + body.join(""));
|
|
|
|
const ec = new elliptic.ec("secp256k1");
|
|
const key = ec.keyFromPrivate(signer);
|
|
const signature = key.sign(hash.substr(2), { canonical: true });
|
|
|
|
const packSig = [
|
|
zeroPadBytes(signature.r.toString(16), 32),
|
|
zeroPadBytes(signature.s.toString(16), 32),
|
|
web3.eth.abi
|
|
.encodeParameter("uint8", signature.recoveryParam)
|
|
.substr(2 + (64 - 2)),
|
|
];
|
|
|
|
return "0x" + packSig.join("");
|
|
};
|
|
|
|
const signAndEncodeVM = async function(
|
|
timestamp,
|
|
nonce,
|
|
emitterChainId,
|
|
emitterAddress,
|
|
sequence,
|
|
data,
|
|
signers,
|
|
guardianSetIndex,
|
|
consistencyLevel
|
|
) {
|
|
const body = [
|
|
web3.eth.abi.encodeParameter("uint32", timestamp).substring(2 + (64 - 8)),
|
|
web3.eth.abi.encodeParameter("uint32", nonce).substring(2 + (64 - 8)),
|
|
web3.eth.abi
|
|
.encodeParameter("uint16", emitterChainId)
|
|
.substring(2 + (64 - 4)),
|
|
web3.eth.abi.encodeParameter("bytes32", emitterAddress).substring(2),
|
|
web3.eth.abi.encodeParameter("uint64", sequence).substring(2 + (64 - 16)),
|
|
web3.eth.abi
|
|
.encodeParameter("uint8", consistencyLevel)
|
|
.substring(2 + (64 - 2)),
|
|
data.substr(2),
|
|
];
|
|
|
|
const hash = web3.utils.soliditySha3(
|
|
web3.utils.soliditySha3("0x" + body.join(""))
|
|
);
|
|
|
|
let signatures = "";
|
|
|
|
for (let i in signers) {
|
|
const ec = new elliptic.ec("secp256k1");
|
|
const key = ec.keyFromPrivate(signers[i]);
|
|
const signature = key.sign(hash.substr(2), { canonical: true });
|
|
|
|
const packSig = [
|
|
web3.eth.abi.encodeParameter("uint8", i).substring(2 + (64 - 2)),
|
|
zeroPadBytes(signature.r.toString(16), 32),
|
|
zeroPadBytes(signature.s.toString(16), 32),
|
|
web3.eth.abi
|
|
.encodeParameter("uint8", signature.recoveryParam)
|
|
.substr(2 + (64 - 2)),
|
|
];
|
|
|
|
signatures += packSig.join("");
|
|
}
|
|
|
|
const vm = [
|
|
web3.eth.abi.encodeParameter("uint8", 1).substring(2 + (64 - 2)),
|
|
web3.eth.abi
|
|
.encodeParameter("uint32", guardianSetIndex)
|
|
.substring(2 + (64 - 8)),
|
|
web3.eth.abi
|
|
.encodeParameter("uint8", signers.length)
|
|
.substring(2 + (64 - 2)),
|
|
|
|
signatures,
|
|
body.join(""),
|
|
].join("");
|
|
|
|
return vm;
|
|
};
|
|
|
|
function zeroPadBytes(value, length) {
|
|
while (value.length < 2 * length) {
|
|
value = "0" + value;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
wait = async (time) => {
|
|
await advanceTimeAndBlock(time);
|
|
// await timeout(time * 1000);
|
|
};
|
|
|
|
timeout = async (ms) => {
|
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
};
|
|
|
|
advanceTimeAndBlock = async (time) => {
|
|
await advanceTime(time);
|
|
await advanceBlock();
|
|
|
|
return Promise.resolve(web3.eth.getBlock("latest"));
|
|
};
|
|
|
|
advanceTime = (time) => {
|
|
return new Promise((resolve, reject) => {
|
|
web3.currentProvider.send(
|
|
{
|
|
jsonrpc: "2.0",
|
|
method: "evm_increaseTime",
|
|
params: [time],
|
|
id: new Date().getTime(),
|
|
},
|
|
(err, result) => {
|
|
if (err) {
|
|
return reject(err);
|
|
}
|
|
return resolve(result);
|
|
}
|
|
);
|
|
});
|
|
};
|
|
|
|
advanceBlock = () => {
|
|
return new Promise((resolve, reject) => {
|
|
web3.currentProvider.send(
|
|
{
|
|
jsonrpc: "2.0",
|
|
method: "evm_mine",
|
|
id: new Date().getTime(),
|
|
},
|
|
(err, result) => {
|
|
if (err) {
|
|
return reject(err);
|
|
}
|
|
const newBlockHash = web3.eth.getBlock("latest").hash;
|
|
|
|
return resolve(newBlockHash);
|
|
}
|
|
);
|
|
});
|
|
};
|
|
|
|
revert = (snapshotId) => {
|
|
return new Promise((resolve, reject) => {
|
|
web3.currentProvider.send(
|
|
{
|
|
jsonrpc: "2.0",
|
|
method: "evm_revert",
|
|
id: new Date().getTime(),
|
|
params: [snapshotId],
|
|
},
|
|
(err, result) => {
|
|
if (err) {
|
|
return reject(err);
|
|
}
|
|
return resolve(result);
|
|
}
|
|
);
|
|
});
|
|
};
|
|
|
|
snapshot = () => {
|
|
return new Promise((resolve, reject) => {
|
|
web3.currentProvider.send(
|
|
{
|
|
jsonrpc: "2.0",
|
|
method: "evm_snapshot",
|
|
id: new Date().getTime(),
|
|
},
|
|
(err, result) => {
|
|
if (err) {
|
|
return reject(err);
|
|
}
|
|
return resolve(result.result);
|
|
}
|
|
);
|
|
});
|
|
};
|