hardhat#web3 TypeScript Examples
The following examples show how to use
hardhat#web3.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: RootBridgeSpec.ts From perpetual-protocol with GNU General Public License v3.0 | 6 votes |
describe("RootBridge Spec", () => {
let admin: string
let alice: string
let rootBridge: RootBridgeInstance
let ambBridgeMock: AMBBridgeMockInstance
let multiTokenMediatorMock: MultiTokenMediatorMockInstance
beforeEach(async () => {
const addresses = await web3.eth.getAccounts()
admin = addresses[0]
alice = addresses[1]
ambBridgeMock = await deployMockAMBBridge()
multiTokenMediatorMock = await deployMockMultiToken()
rootBridge = await deployRootBridge(ambBridgeMock.address, multiTokenMediatorMock.address)
})
it("updatePriceFeed", async () => {
const price = toDecimal(400)
const timestamp = "123456789"
const roundId = "999"
const priceFeed = await deployL2MockPriceFeed(toFullDigit(400))
await rootBridge.setPriceFeed(admin)
const receipt = await rootBridge.updatePriceFeed(
priceFeed.address,
utils.formatBytes32String("ETH"),
price,
timestamp,
roundId,
)
await expectEvent.inTransaction(receipt.tx, priceFeed, "PriceFeedDataSet", {
price: price.d,
timestamp: timestamp,
roundId: roundId,
})
})
})
Example #2
Source File: MetaTxGatewaySpec.ts From perpetual-protocol with GNU General Public License v3.0 | 6 votes |
async before(): Promise<void> {
const accounts = await web3.eth.getAccounts()
this.admin = accounts[0]
this.alice = accounts[1]
this.relayer = accounts[2]
this.l1ChainId = 1234
const MetaTxRecipientMockArtifact = await artifacts.readArtifact(
"src/mock/mocks/MetaTxRecipientMock.sol:MetaTxRecipientMock",
)
this.metaTxGateway = await deployMetaTxGateway("Test", "1", this.l1ChainId)
this.metaTxRecipientMock = ((await new web3.eth.Contract(MetaTxRecipientMockArtifact.abi)
.deploy({
data: MetaTxRecipientMockArtifact.bytecode,
arguments: [this.metaTxGateway.address],
})
.send({
from: this.admin,
})) as unknown) as MetaTxRecipientMock
await this.metaTxGateway.addToWhitelists(this.metaTxRecipientMock.options.address)
this.domain = {
name: "Test",
version: "1",
chainId: this.l1ChainId,
verifyingContract: this.metaTxGateway.address,
}
}
Example #3
Source File: AmmSpecClass.ts From perpetual-protocol with GNU General Public License v3.0 | 6 votes |
async before(): Promise<void> {
const accounts = await web3.eth.getAccounts()
this.admin = accounts[0]
this.otherA = accounts[1]
this.priceFeed = await deployL2MockPriceFeed(DEFAULT_PRICE)
await this.deployAMM()
this.fundingPeriod = (await this.amm.fundingPeriod()).toNumber()
this.fundingBufferPeriod = (await this.amm.fundingBufferPeriod()).toNumber()
}
Example #4
Source File: StakingReserveSpec.ts From perpetual-protocol with GNU General Public License v3.0 | 6 votes |
// skip, won't be in v1
describe.skip("StakingReserve Spec", () => {
let admin: string
let alice: string
let perpToken: PerpTokenMockInstance
let clearingHouse: string
let stakingReserve: StakingReserveInstance
let vestingPeriod: number
beforeEach(async () => {
const addresses = await web3.eth.getAccounts()
admin = addresses[0]
alice = addresses[1]
clearingHouse = addresses[2]
const supplyScheduleMock = addresses[2]
vestingPeriod = 1
perpToken = await deployPerpTokenMock()
stakingReserve = await deployStakingReserve(
perpToken.address,
supplyScheduleMock,
clearingHouse,
new BN(vestingPeriod),
)
await stakingReserve.setRewardsDistribution(admin)
})
describe("claimFeesAndVestedReward", () => {
it("can't claim if there's no reward", async () => {
await expectRevert(stakingReserve.claimFeesAndVestedReward(), "no vested reward or fee")
})
})
})
Example #5
Source File: web3.ts From perpetual-protocol with GNU General Public License v3.0 | 6 votes |
export function signEIP712MetaTx(signer: string, domain: EIP712Domain, metaTx: MetaTx): Promise<SignedResponse> {
const dataToSign = {
types: {
EIP712Domain: EIP712DomainTypes,
MetaTransaction: MetaTxTypes,
},
domain,
primaryType: "MetaTransaction",
message: metaTx,
}
return new Promise((resolve, reject) => {
const send = getSendFunction(web3)
send(
{
jsonrpc: "2.0",
id: 999999999999,
method: "eth_signTypedData_v4",
params: [signer, dataToSign],
},
async function(err: any, result: any) {
if (err) {
reject(err)
}
const signature = result.result.substring(2)
resolve({
signature,
r: "0x" + signature.substring(0, 64),
s: "0x" + signature.substring(64, 128),
v: parseInt(signature.substring(128, 130), 16),
})
},
)
})
}
Example #6
Source File: web3.ts From perpetual-protocol with GNU General Public License v3.0 | 6 votes |
export function changeBlockTime(time: number): Promise<void> {
return new Promise((resolve, reject) => {
const send = getSendFunction(web3)
send(
{
jsonrpc: "2.0",
method: "evm_increaseTime",
params: [time],
id: 0,
},
(err: Error | null, res: any) => {
if (err) {
reject(err)
} else {
resolve(res)
}
},
)
})
}
Example #7
Source File: web3.ts From perpetual-protocol with GNU General Public License v3.0 | 6 votes |
function getSendFunction(web3: Web3) {
const provider = web3.currentProvider
if (typeof provider === "string") {
throw new TypeError("web3.currentProvider should not be a string")
}
if (provider === null || provider === undefined) {
throw new TypeError("web3.currentProvider should not be null or undefined")
}
if (!provider.send) {
throw new TypeError("web3.currentProvider.send() does not exist")
}
return provider.send
}
Example #8
Source File: contract.ts From perpetual-protocol with GNU General Public License v3.0 | 6 votes |
export async function deployAmm(params: {
deployer: string
quoteAssetTokenAddr: string
priceFeedAddr: string
fluctuation: BN
priceFeedKey?: string
fundingPeriod?: BN
baseAssetReserve?: BN
quoteAssetReserve?: BN
tollRatio?: BN
spreadRatio?: BN
}): Promise<AmmFakeInstance> {
const {
deployer,
quoteAssetTokenAddr,
priceFeedAddr,
fluctuation,
fundingPeriod = new BN(8 * 60 * 60), // 8hr
baseAssetReserve = toFullDigit(100),
quoteAssetReserve = toFullDigit(1000),
priceFeedKey = "ETH",
tollRatio = new BN(0),
spreadRatio = new BN(0),
} = params
return AmmFake.new(
quoteAssetReserve,
baseAssetReserve,
toFullDigit(0.9), // tradeLimitRatio
fundingPeriod,
priceFeedAddr,
web3.utils.asciiToHex(priceFeedKey),
quoteAssetTokenAddr,
fluctuation,
tollRatio,
spreadRatio,
{ from: deployer },
)
}
Example #9
Source File: BaseBridgeSpec.ts From perpetual-protocol with GNU General Public License v3.0 | 5 votes |
describe("BaseBridgeSpec Spec", () => {
let admin: string
let alice: string
// let depositProxy!: DepositProxyInstance
let rootBridge: RootBridgeInstance
let quoteToken: ERC20FakeInstance
let ambBridgeMock: AMBBridgeMockInstance
let multiTokenMediatorMock: MultiTokenMediatorMockInstance
beforeEach(async () => {
const addresses = await web3.eth.getAccounts()
admin = addresses[0]
alice = addresses[1]
ambBridgeMock = await deployMockAMBBridge()
multiTokenMediatorMock = await deployMockMultiToken()
rootBridge = await deployRootBridge(ambBridgeMock.address, multiTokenMediatorMock.address)
quoteToken = await deployErc20Fake(toFullDigit(10000), "NAME", "SYMBOL", new BN("6"))
})
it("setAMBBridge", async () => {
const receipt = await rootBridge.setAMBBridge(alice)
expect(await rootBridge.ambBridge()).eq(alice)
await expectEvent.inTransaction(receipt.tx, rootBridge, "BridgeChanged", { bridge: alice })
})
it("setMultiTokenMediator", async () => {
const receipt = await rootBridge.setMultiTokenMediator(alice)
expect(await rootBridge.multiTokenMediator()).eq(alice)
await expectEvent.inTransaction(receipt.tx, rootBridge, "MultiTokenMediatorChanged", { mediator: alice })
})
it("multiTokenTransfer", async () => {
await quoteToken.approve(rootBridge.address, toFullDigit(100))
const receipt = await rootBridge.erc20Transfer(quoteToken.address, alice, toDecimal(100))
await expectEvent.inTransaction(receipt.tx, rootBridge, "Relayed", {
token: quoteToken.address,
receiver: alice,
amount: toFullDigit(100),
})
// verify balance of the token bridge
const digit = new BN(10).pow(await quoteToken.decimals())
const aHundred = new BN(100)
expect(await quoteToken.balanceOf(multiTokenMediatorMock.address)).eq(aHundred.mul(digit))
})
it("should fail when multiTokenMediator is not implementing relayTokens()", async () => {
await rootBridge.setMultiTokenMediator(quoteToken.address)
await quoteToken.approve(rootBridge.address, toFullDigit(100))
await expectRevert(
rootBridge.erc20Transfer(quoteToken.address, alice, toDecimal(100)),
"Transaction reverted: function selector was not recognized and there's no fallback function",
)
})
it("force error, only owner can setAMBBridge", async () => {
await expectRevert(
rootBridge.setAMBBridge(alice, { from: alice }),
"PerpFiOwnableUpgrade: caller is not the owner",
)
})
it("force error, only owner can multiTokenMediator", async () => {
await expectRevert(
rootBridge.setMultiTokenMediator(alice, { from: alice }),
"PerpFiOwnableUpgrade: caller is not the owner",
)
})
it("force error, transfer zero amount", async () => {
await expectRevert(rootBridge.erc20Transfer(quoteToken.address, alice, toDecimal(0)), "amount is zero")
})
})
Example #10
Source File: ClientBridgeSpec.ts From perpetual-protocol with GNU General Public License v3.0 | 5 votes |
describe("ClientBridgeSpec Spec", () => {
let admin: string
let alice: string
let trustForwarder: string
let clientBridge: ClientBridgeInstance
let quoteToken: ERC20FakeInstance
let ambBridgeMock: AMBBridgeMockInstance
let multiTokenMediatorMock: MultiTokenMediatorMockInstance
beforeEach(async () => {
const addresses = await web3.eth.getAccounts()
admin = addresses[0]
alice = addresses[1]
trustForwarder = addresses[2]
ambBridgeMock = await deployMockAMBBridge()
multiTokenMediatorMock = await deployMockMultiToken()
clientBridge = await deployClientBridge(ambBridgeMock.address, multiTokenMediatorMock.address, trustForwarder)
quoteToken = await deployErc20Fake(toFullDigit(10000), "NAME", "SYMBOL", new BN("6"))
})
describe("withdraw with minimum amount", () => {
beforeEach(async () => {
await clientBridge.setMinWithdrawalAmount(quoteToken.address, toDecimal(10))
})
it("same as minimum withdrawal amount", async () => {
await quoteToken.approve(clientBridge.address, toFullDigit(10), { from: admin })
await clientBridge.erc20Transfer(quoteToken.address, alice, toDecimal(10), { from: admin })
// verify balance of the token bridge
const digit = new BN(10).pow(await quoteToken.decimals())
const aTen = new BN(10)
expect(await quoteToken.balanceOf(multiTokenMediatorMock.address)).eq(aTen.mul(digit))
})
it("more than minimum withdrawal amount", async () => {
await quoteToken.approve(clientBridge.address, toFullDigit(100), { from: admin })
await clientBridge.erc20Transfer(quoteToken.address, alice, toDecimal(100), { from: admin })
// verify balance of the token bridge
const digit = new BN(10).pow(await quoteToken.decimals())
const aHundred = new BN(100)
expect(await quoteToken.balanceOf(multiTokenMediatorMock.address)).eq(aHundred.mul(digit))
})
it("force error, less than minimum withdrawal amount", async () => {
await expectRevert(
clientBridge.erc20Transfer(quoteToken.address, alice, toDecimal(9.9)),
"amount is too small",
)
})
})
it("set minimum withdrawal amount", async () => {
await clientBridge.setMinWithdrawalAmount(quoteToken.address, toDecimal(100))
expect(await clientBridge.minWithdrawalAmountMap(quoteToken.address)).eq(toFullDigit(100))
// can be overridden
await clientBridge.setMinWithdrawalAmount(quoteToken.address, toDecimal(5))
expect(await clientBridge.minWithdrawalAmountMap(quoteToken.address)).eq(toFullDigit(5))
})
it("force error, only owner can setMinWithdrawalAmount", async () => {
await expectRevert(
clientBridge.setMinWithdrawalAmount(quoteToken.address, toDecimal(100), { from: alice }),
"PerpFiOwnableUpgrade: caller is not the owner",
)
})
})
Example #11
Source File: PerpFiOwnableSpec.ts From perpetual-protocol with GNU General Public License v3.0 | 5 votes |
describe("PerpFiOwnableUpgrade UT", () => {
let perpFiOwnable: PerpFiOwnableUpgradeFakeInstance
let addresses: string[]
let admin: string
let alice: string
beforeEach(async () => {
addresses = await web3.eth.getAccounts()
admin = addresses[0]
alice = addresses[1]
perpFiOwnable = (await PerpFiOwnableUpgradeFake.new()) as PerpFiOwnableUpgradeFakeInstance
await perpFiOwnable.initialize()
})
it("transfer ownership", async () => {
await perpFiOwnable.setOwner(alice)
const r = await perpFiOwnable.updateOwner({ from: alice })
expectEvent.inTransaction(r.tx, perpFiOwnable, "OwnershipTransferred", {
previousOwner: admin,
newOwner: alice,
})
})
it("transfer ownership and set owner to another", async () => {
await perpFiOwnable.setOwner(alice)
const r = await perpFiOwnable.updateOwner({ from: alice })
expectEvent.inTransaction(r.tx, perpFiOwnable, "OwnershipTransferred")
// only owner can set owner, now owner is alice
await perpFiOwnable.setOwner(admin, { from: alice })
expect(await perpFiOwnable.candidate()).eq(admin)
})
it("force error, only owner can call setOwner", async () => {
await expectRevert(
perpFiOwnable.setOwner(alice, { from: alice }),
"PerpFiOwnableUpgrade: caller is not the owner",
)
})
it("force error set current owner", async () => {
await expectRevert(perpFiOwnable.setOwner(admin), "PerpFiOwnableUpgrade: same as original")
})
it("force error, update owner but caller not the new owner", async () => {
await perpFiOwnable.setOwner(alice)
await expectRevert(perpFiOwnable.updateOwner({ from: admin }), "PerpFiOwnableUpgrade: not the new owner")
})
it("force error, update owner without set a new owner first", async () => {
await expectRevert(
perpFiOwnable.updateOwner({ from: admin }),
"PerpFiOwnableUpgrade: candidate is zero address",
)
})
it("force error, can not update twice", async () => {
await perpFiOwnable.setOwner(alice)
const r = await perpFiOwnable.updateOwner({ from: alice })
expectEvent.inTransaction(r.tx, perpFiOwnable, "OwnershipTransferred")
await expectRevert(
perpFiOwnable.updateOwner({ from: alice }),
"PerpFiOwnableUpgrade: candidate is zero address",
)
})
})
Example #12
Source File: AmmReaderSpec.ts From perpetual-protocol with GNU General Public License v3.0 | 5 votes |
describe("AmmReader Unit Test", () => {
const ETH_PRICE = 100
const ETH_BYTES32 = "0x4554480000000000000000000000000000000000000000000000000000000000"
let amm: AmmFakeInstance
let ammReader: AmmReaderInstance
let l2PriceFeed: L2PriceFeedMockInstance
let quoteToken: ERC20FakeInstance
let admin: string
beforeEach(async () => {
const addresses = await web3.eth.getAccounts()
admin = addresses[0]
l2PriceFeed = await deployL2MockPriceFeed(toFullDigit(ETH_PRICE))
quoteToken = await deployErc20Fake(toFullDigit(20000000))
amm = await deployAmm({
deployer: admin,
quoteAssetTokenAddr: quoteToken.address,
priceFeedAddr: l2PriceFeed.address,
fluctuation: toFullDigit(0),
fundingPeriod: new BN(3600), // 1 hour
})
await amm.setCounterParty(admin)
ammReader = await deployAmmReader()
})
it("verify inputs & outputs", async () => {
const {
quoteAssetReserve,
baseAssetReserve,
tradeLimitRatio,
fundingPeriod,
quoteAssetSymbol,
baseAssetSymbol,
priceFeedKey,
priceFeed,
} = await ammReader.getAmmStates(amm.address)
expect(quoteAssetReserve).to.eq(toFullDigitStr(1000))
expect(baseAssetReserve).to.eq(toFullDigitStr(100))
expect(tradeLimitRatio).to.eq(toFullDigitStr(0.9))
expect(fundingPeriod).to.eq("3600")
expect(quoteAssetSymbol).to.eq("symbol")
expect(baseAssetSymbol).to.eq("ETH")
expect(priceFeedKey).to.eq(ETH_BYTES32)
expect(priceFeed).to.eq(l2PriceFeed.address)
})
})
Example #13
Source File: ChainlinkL1Spec.ts From perpetual-protocol with GNU General Public License v3.0 | 4 votes |
describe("chainlinkL1 Spec", () => {
let addresses: string[]
let chainlinkL1!: ChainlinkL1Instance
let chainlinkAggregator!: ChainlinkAggregatorMockInstance
let rootBridgeMock!: RootBridgeMockInstance
const EMPTY_ADDRESS = "0x0000000000000000000000000000000000000000"
beforeEach(async () => {
addresses = await web3.eth.getAccounts()
chainlinkAggregator = await deployChainlinkAggregatorMock()
rootBridgeMock = await deployRootBridgeMock()
})
function stringToBytes32(str: string): string {
return web3.utils.asciiToHex(str)
}
function fromBytes32(str: string): string {
return web3.utils.hexToUtf8(str)
}
describe("initialize()", () => {
it("force error, RootBridge address cannot be address(0)", async () => {
await expectRevert(deployChainlinkL1(EMPTY_ADDRESS, addresses[1]), "empty address")
})
it("force error, PriceFeedL2 address cannot be address(0)", async () => {
await expectRevert(deployChainlinkL1(addresses[1], EMPTY_ADDRESS), "empty address")
})
})
describe("setRootBridge(), setPriceFeedL2()", () => {
beforeEach(async () => {
chainlinkL1 = await deployChainlinkL1(rootBridgeMock.address, addresses[1])
})
it("set the address of RootBridge", async () => {
const receipt = await chainlinkL1.setRootBridge(addresses[1])
await expectEvent.inTransaction(receipt.tx, chainlinkL1, "RootBridgeChanged", { rootBridge: addresses[1] })
expect(await chainlinkL1.rootBridge()).eq(addresses[1])
})
it("set the address of PriceFeedL2", async () => {
const receipt = await chainlinkL1.setPriceFeedL2(addresses[1])
await expectEvent.inTransaction(receipt.tx, chainlinkL1, "PriceFeedL2Changed", {
priceFeedL2: addresses[1],
})
expect(await chainlinkL1.priceFeedL2Address()).eq(addresses[1])
})
// expectRevert section
it("force error, RootBridge address cannot be address(0)", async () => {
await expectRevert(chainlinkL1.setRootBridge(EMPTY_ADDRESS), "empty address")
})
it("force error, PriceFeedL2 address cannot be address(0)", async () => {
await expectRevert(chainlinkL1.setPriceFeedL2(EMPTY_ADDRESS), "empty address")
})
})
describe("addAggregator", () => {
beforeEach(async () => {
chainlinkL1 = await deployChainlinkL1(rootBridgeMock.address, addresses[1])
})
it("getAggregator with existed aggregator key", async () => {
await chainlinkL1.addAggregator(stringToBytes32("ETH"), chainlinkAggregator.address)
expect(fromBytes32(await chainlinkL1.priceFeedKeys(0))).eq("ETH")
expect(await chainlinkL1.getAggregator(stringToBytes32("ETH"))).eq(chainlinkAggregator.address)
})
it("getAggregator with non-existed aggregator key", async () => {
await chainlinkL1.addAggregator(stringToBytes32("ETH"), chainlinkAggregator.address)
expect(await chainlinkL1.getAggregator(stringToBytes32("BTC"))).eq(EMPTY_ADDRESS)
})
it("add multi aggregators", async () => {
await chainlinkL1.addAggregator(stringToBytes32("ETH"), chainlinkAggregator.address)
await chainlinkL1.addAggregator(stringToBytes32("BTC"), addresses[1])
await chainlinkL1.addAggregator(stringToBytes32("LINK"), addresses[2])
expect(fromBytes32(await chainlinkL1.priceFeedKeys(0))).eq("ETH")
expect(await chainlinkL1.getAggregator(stringToBytes32("ETH"))).eq(chainlinkAggregator.address)
expect(fromBytes32(await chainlinkL1.priceFeedKeys(2))).eq("LINK")
expect(await chainlinkL1.getAggregator(stringToBytes32("LINK"))).eq(addresses[2])
})
it("force error, addAggregator with zero address", async () => {
await expectRevert(chainlinkL1.addAggregator(stringToBytes32("ETH"), EMPTY_ADDRESS), "empty address")
})
})
describe("removeAggregator", () => {
beforeEach(async () => {
chainlinkL1 = await deployChainlinkL1(rootBridgeMock.address, addresses[1])
})
it("remove 1 aggregator when there's only 1", async () => {
await chainlinkL1.addAggregator(stringToBytes32("ETH"), chainlinkAggregator.address)
await chainlinkL1.removeAggregator(stringToBytes32("ETH"))
// cant use expectRevert because the error message is different between CI and local env
let error
try {
await chainlinkL1.priceFeedKeys(0)
} catch (e) {
error = e
}
expect(error).not.eq(undefined)
expect(await chainlinkL1.getAggregator(stringToBytes32("ETH"))).eq(EMPTY_ADDRESS)
})
it("remove 1 aggregator when there're 2", async () => {
await chainlinkL1.addAggregator(stringToBytes32("ETH"), chainlinkAggregator.address)
await chainlinkL1.addAggregator(stringToBytes32("BTC"), chainlinkAggregator.address)
await chainlinkL1.removeAggregator(stringToBytes32("ETH"))
expect(fromBytes32(await chainlinkL1.priceFeedKeys(0))).eq("BTC")
expect(await chainlinkL1.getAggregator(stringToBytes32("ETH"))).eq(EMPTY_ADDRESS)
expect(await chainlinkL1.getAggregator(stringToBytes32("BTC"))).eq(chainlinkAggregator.address)
})
})
describe("updateLatestRoundData()", () => {
const _messageId = 20
const _messageIdBytes32 = "0x0000000000000000000000000000000000000000000000000000000000000014"
beforeEach(async () => {
chainlinkL1 = await deployChainlinkL1(rootBridgeMock.address, addresses[1])
await chainlinkL1.addAggregator(stringToBytes32("ETH"), chainlinkAggregator.address)
await rootBridgeMock.mockSetMessageId(_messageId)
await chainlinkAggregator.mockAddAnswer(8, 12345678, 1, 200000000000, 1)
})
it("get latest data", async () => {
const receipt = await chainlinkL1.updateLatestRoundData(stringToBytes32("ETH"))
await expectEvent.inTransaction(receipt.tx, chainlinkL1, "PriceUpdateMessageIdSent", {
messageId: _messageIdBytes32,
})
// reported price should be normalized to 18 decimals
expect(await rootBridgeMock.price()).eq(new BN("123456780000000000"))
})
it("get latest data, a specified keeper is not required", async () => {
const receipt = await chainlinkL1.updateLatestRoundData(stringToBytes32("ETH"), { from: addresses[1] })
await expectEvent.inTransaction(receipt.tx, chainlinkL1, "PriceUpdateMessageIdSent", {
messageId: _messageIdBytes32,
})
})
// expectRevert section
it("force error, get non-existing aggregator", async () => {
const _wrongPriceFeedKey = "Ha"
await expectRevert(chainlinkL1.updateLatestRoundData(stringToBytes32(_wrongPriceFeedKey)), "empty address")
})
it("force error, timestamp equal to 0", async () => {
await chainlinkAggregator.mockAddAnswer(8, 41, 1, 0, 1)
await expectRevert(chainlinkL1.updateLatestRoundData(stringToBytes32("ETH")), "incorrect timestamp")
})
it("force error, same timestamp as previous", async () => {
// first update should pass
await chainlinkL1.updateLatestRoundData(stringToBytes32("ETH"))
expect(await chainlinkL1.prevTimestampMap(stringToBytes32("ETH"))).eq(new BN(200000000000))
// second update with the same timestamp should fail
await expectRevert(chainlinkL1.updateLatestRoundData(stringToBytes32("ETH")), "incorrect timestamp")
})
})
})
Example #14
Source File: FeeTokenPoolDispatcherL1Spec.ts From perpetual-protocol with GNU General Public License v3.0 | 4 votes |
describe("FeeTokenPoolDispatcherL1Spec", () => {
let admin: string
let alice: string
let feeTokenPoolDispatcher: FeeTokenPoolDispatcherL1Instance
let feeRewardPoolMock1: FeeRewardPoolMockInstance
let feeRewardPoolMock2: FeeRewardPoolMockInstance
let usdt: ERC20FakeInstance
let usdc: ERC20FakeInstance
beforeEach(async () => {
const addresses = await web3.eth.getAccounts()
admin = addresses[0]
alice = addresses[1]
usdt = await deployErc20Fake(toFullDigit(2000000))
usdc = await deployErc20Fake(toFullDigit(2000000))
feeRewardPoolMock1 = await deployFeeRewardPoolMock()
feeRewardPoolMock2 = await deployFeeRewardPoolMock()
feeTokenPoolDispatcher = await deployFeeTokenPoolDispatcherL1()
await feeRewardPoolMock1.setToken(usdt.address)
await feeRewardPoolMock2.setToken(usdc.address)
await usdt.transfer(alice, toFullDigit(2000))
await usdc.transfer(alice, toFullDigit(2000))
})
describe("transferToFeeRewardPool()", () => {
it("feeRewardPool should receive one token", async () => {
await feeTokenPoolDispatcher.addFeeRewardPool(feeRewardPoolMock1.address)
await usdt.transfer(feeTokenPoolDispatcher.address, toFullDigit(1000))
const receipt = await feeTokenPoolDispatcher.transferToFeeRewardPool()
expectEvent.inTransaction(receipt.tx, feeTokenPoolDispatcher, "FeeTransferred", {
token: usdt.address,
feeRewardPool: feeRewardPoolMock1.address,
amount: toFullDigit(1000),
})
expect(await usdt.balanceOf(feeTokenPoolDispatcher.address)).to.eq(toFullDigit(0))
expect(await usdt.balanceOf(feeRewardPoolMock1.address)).to.eq(toFullDigit(1000))
})
it("one feeRewardPool CAN receive two tokens", async () => {
await feeTokenPoolDispatcher.addFeeRewardPool(feeRewardPoolMock1.address)
await feeTokenPoolDispatcher.addFeeRewardPool(feeRewardPoolMock2.address)
await usdt.transfer(feeTokenPoolDispatcher.address, toFullDigit(1000))
await usdc.transfer(feeTokenPoolDispatcher.address, toFullDigit(2000))
const receipt = await feeTokenPoolDispatcher.transferToFeeRewardPool()
expectEvent.inTransaction(
receipt.tx,
feeTokenPoolDispatcher,
"FeeTransferred",
{
token: usdt.address,
feeRewardPool: feeRewardPoolMock1.address,
amount: toFullDigit(1000),
},
{
token: usdc.address,
feeRewardPool: feeRewardPoolMock2.address,
amount: toFullDigit(2000),
},
)
expect(await usdt.balanceOf(feeRewardPoolMock1.address)).to.eq(toFullDigit(1000))
expect(await usdc.balanceOf(feeRewardPoolMock2.address)).to.eq(toFullDigit(2000))
})
it("two feeRewardPool should receive two tokens", async () => {
await feeTokenPoolDispatcher.addFeeRewardPool(feeRewardPoolMock1.address)
await usdt.transfer(feeTokenPoolDispatcher.address, toFullDigit(1000))
await feeTokenPoolDispatcher.addFeeRewardPool(feeRewardPoolMock2.address)
await usdc.transfer(feeTokenPoolDispatcher.address, toFullDigit(2000))
const receipt = await feeTokenPoolDispatcher.transferToFeeRewardPool()
expectEvent.inTransaction(receipt.tx, feeTokenPoolDispatcher, "FeeTransferred", {
token: usdt.address,
feeRewardPool: feeRewardPoolMock1.address,
amount: toFullDigit(1000),
})
expectEvent.inTransaction(receipt.tx, feeTokenPoolDispatcher, "FeeTransferred", {
token: usdc.address,
feeRewardPool: feeRewardPoolMock2.address,
amount: toFullDigit(2000),
})
expect(await usdt.balanceOf(feeRewardPoolMock1.address)).to.eq(toFullDigit(1000))
expect(await usdc.balanceOf(feeRewardPoolMock2.address)).to.eq(toFullDigit(2000))
})
it("two feeRewardPool should only receive one token as there is another one token added with no balance", async () => {
await feeTokenPoolDispatcher.addFeeRewardPool(feeRewardPoolMock1.address)
await feeTokenPoolDispatcher.addFeeRewardPool(feeRewardPoolMock2.address)
await usdt.transfer(feeTokenPoolDispatcher.address, toFullDigit(1000))
const receipt = await feeTokenPoolDispatcher.transferToFeeRewardPool()
expectEvent.inTransaction(receipt.tx, feeTokenPoolDispatcher, "FeeTransferred", {
token: usdt.address,
feeRewardPool: feeRewardPoolMock1.address,
amount: toFullDigit(1000),
})
expect(await usdt.balanceOf(feeRewardPoolMock1.address)).to.eq(toFullDigit(1000))
expect(await usdc.balanceOf(feeRewardPoolMock2.address)).to.eq(toFullDigit(0))
})
it("force error, no feeTokens set yet", async () => {
await expectRevert(feeTokenPoolDispatcher.transferToFeeRewardPool(), "feeTokens not set yet")
})
it("force error, balances of all tokens are zero", async () => {
await feeTokenPoolDispatcher.addFeeRewardPool(feeRewardPoolMock1.address)
await expectRevert(feeTokenPoolDispatcher.transferToFeeRewardPool(), "fee is now zero")
})
})
describe("addFeeRewardPool()", () => {
it("two feeRewardPool should be added", async () => {
const receipt1 = await feeTokenPoolDispatcher.addFeeRewardPool(feeRewardPoolMock1.address)
expectEvent.inTransaction(receipt1.tx, feeTokenPoolDispatcher, "FeeRewardPoolAdded", {
token: usdt.address,
feeRewardPool: feeRewardPoolMock1.address,
})
const receipt2 = await feeTokenPoolDispatcher.addFeeRewardPool(feeRewardPoolMock2.address)
expectEvent.inTransaction(receipt2.tx, feeTokenPoolDispatcher, "FeeRewardPoolAdded", {
token: usdc.address,
feeRewardPool: feeRewardPoolMock2.address,
})
expect(await feeTokenPoolDispatcher.feeRewardPoolMap(usdt.address)).to.eq(feeRewardPoolMock1.address)
expect(await feeTokenPoolDispatcher.feeRewardPoolMap(usdc.address)).to.eq(feeRewardPoolMock2.address)
expect(await feeTokenPoolDispatcher.feeTokens(1)).to.eq(usdc.address)
expect(await feeTokenPoolDispatcher.getFeeTokenLength()).to.eq(2)
expect(await feeTokenPoolDispatcher.isFeeTokenExisted(usdt.address)).to.eq(true)
expect(await feeTokenPoolDispatcher.isFeeTokenExisted(usdc.address)).to.eq(true)
})
it("force error, onlyOwner", async () => {
await expectRevert(
feeTokenPoolDispatcher.addFeeRewardPool(feeRewardPoolMock1.address, { from: alice }),
"PerpFiOwnableUpgrade: caller is not the owner",
)
})
it("force error, feeRewardPool is zero address", async () => {
await expectRevert.unspecified(feeTokenPoolDispatcher.addFeeRewardPool(EMPTY_ADDRESS))
})
it("force error, feeToken is already existed", async () => {
await feeTokenPoolDispatcher.addFeeRewardPool(feeRewardPoolMock1.address)
await expectRevert(feeTokenPoolDispatcher.addFeeRewardPool(feeRewardPoolMock1.address), "invalid input")
})
})
describe("removeFeeRewardPool()", () => {
it("one feeRewardPool should be removed", async () => {
await feeTokenPoolDispatcher.addFeeRewardPool(feeRewardPoolMock1.address)
const receipt1 = await feeTokenPoolDispatcher.removeFeeRewardPool(feeRewardPoolMock1.address)
expectEvent.inTransaction(receipt1.tx, feeTokenPoolDispatcher, "FeeRewardPoolRemoved", {
token: usdt.address,
feeRewardPool: feeRewardPoolMock1.address,
})
expect(await feeTokenPoolDispatcher.getFeeTokenLength()).to.eq(0)
expect(await feeTokenPoolDispatcher.feeRewardPoolMap(usdt.address)).to.eq(EMPTY_ADDRESS)
expect(await feeTokenPoolDispatcher.isFeeTokenExisted(usdt.address)).to.eq(false)
})
it("two feeRewardPool are added but only one should be removed", async () => {
await feeTokenPoolDispatcher.addFeeRewardPool(feeRewardPoolMock1.address)
await feeTokenPoolDispatcher.addFeeRewardPool(feeRewardPoolMock2.address)
const receipt1 = await feeTokenPoolDispatcher.removeFeeRewardPool(feeRewardPoolMock1.address)
expectEvent.inTransaction(receipt1.tx, feeTokenPoolDispatcher, "FeeRewardPoolRemoved", {
token: usdt.address,
feeRewardPool: feeRewardPoolMock1.address,
})
expect(await feeTokenPoolDispatcher.getFeeTokenLength()).to.eq(1)
expect(await feeTokenPoolDispatcher.feeRewardPoolMap(usdc.address)).to.eq(feeRewardPoolMock2.address)
expect(await feeTokenPoolDispatcher.feeTokens(0)).to.eq(usdc.address)
expect(await feeTokenPoolDispatcher.isFeeTokenExisted(usdt.address)).to.eq(false)
expect(await feeTokenPoolDispatcher.isFeeTokenExisted(usdc.address)).to.eq(true)
})
it("feeRewardPool is added, removed and then added again", async () => {
await feeTokenPoolDispatcher.addFeeRewardPool(feeRewardPoolMock1.address)
await feeTokenPoolDispatcher.removeFeeRewardPool(feeRewardPoolMock1.address)
await feeTokenPoolDispatcher.addFeeRewardPool(feeRewardPoolMock2.address)
expect(await feeTokenPoolDispatcher.getFeeTokenLength()).to.eq(1)
expect(await feeTokenPoolDispatcher.feeRewardPoolMap(usdt.address)).to.eq(EMPTY_ADDRESS)
expect(await feeTokenPoolDispatcher.feeRewardPoolMap(usdc.address)).to.eq(feeRewardPoolMock2.address)
expect(await feeTokenPoolDispatcher.feeTokens(0)).to.eq(usdc.address)
})
it("should transfer reward to FeeRewardPool before removeFeeRewardPool", async () => {
await feeTokenPoolDispatcher.addFeeRewardPool(feeRewardPoolMock1.address)
await usdt.transfer(feeTokenPoolDispatcher.address, 1)
// TODO expect tollPool call usdt.transfer(feeRewardPoolMock1), feeRewardPoolMock1.notifyRewardAmount
// let's use ethers/waffle when writing new unit test. it's hard to write unit test without mock lib
await feeTokenPoolDispatcher.removeFeeRewardPool(feeRewardPoolMock1.address)
expect(await usdt.balanceOf(feeTokenPoolDispatcher.address)).eq(0)
})
it("force error, onlyOwner", async () => {
await feeTokenPoolDispatcher.addFeeRewardPool(feeRewardPoolMock1.address)
await expectRevert(
feeTokenPoolDispatcher.removeFeeRewardPool(feeRewardPoolMock1.address, { from: alice }),
"PerpFiOwnableUpgrade: caller is not the owner",
)
})
it("force error, token is zero address", async () => {
await feeTokenPoolDispatcher.addFeeRewardPool(feeRewardPoolMock1.address)
await expectRevert(feeTokenPoolDispatcher.removeFeeRewardPool(EMPTY_ADDRESS), "invalid input")
})
it("force error, feeToken does not exist", async () => {
await expectRevert(
feeTokenPoolDispatcher.removeFeeRewardPool(feeRewardPoolMock1.address),
"token does not exist",
)
})
})
})
Example #15
Source File: KeeperRewardSpec.ts From perpetual-protocol with GNU General Public License v3.0 | 4 votes |
describe("Keeper reward L1/L2 Spec", () => {
let addresses: string[]
let admin: string
let fakeAmm: string
let alice: string
let chainlinkL1Mock!: ChainlinkL1MockInstance
let clearingHouseMock!: ClearingHouseMockInstance
let KeeperRewardL1: KeeperRewardL1Instance
let KeeperRewardL2: KeeperRewardL2Instance
let perpToken: PerpTokenInstance
const EMPTY_ADDRESS = "0x0000000000000000000000000000000000000000"
beforeEach(async () => {
addresses = await web3.eth.getAccounts()
admin = addresses[0]
fakeAmm = addresses[1]
alice = addresses[2]
addresses = await web3.eth.getAccounts()
chainlinkL1Mock = await deployChainlinkL1Mock()
clearingHouseMock = await deployClearingHouseMock()
perpToken = await deployPerpToken(toFullDigit(1000))
KeeperRewardL1 = await deployL1KeeperReward(perpToken.address)
KeeperRewardL2 = await deployL2KeeperReward(perpToken.address)
})
// ClearingHouse
// 3e09fa10 => payFunding(address)
// 36405257 => settlePosition(address)
// ChainlinkL1
// f463e18e => updateLatestRoundData(bytes32)
describe("KeeperRewardBase", () => {
it("setKeeperFunction, set single task", async () => {
await KeeperRewardL1.setKeeperFunctions(["0x3e09fa10"], [clearingHouseMock.address], [toFullDigit(1)])
const task = await KeeperRewardL1.tasksMap("0x3e09fa10")
expect(task[0]).eq(clearingHouseMock.address)
expect(task[1]).eq(toFullDigit(1))
})
it("setKeeperFunction, set multi tasks", async () => {
await KeeperRewardL1.setKeeperFunctions(
["0x3e09fa10", "0x36405257", "0xf463e18e"],
[clearingHouseMock.address, clearingHouseMock.address, chainlinkL1Mock.address],
[toFullDigit(1), toFullDigit(5), toFullDigit(10)],
)
const task0 = await KeeperRewardL1.tasksMap("0x3e09fa10")
expect(task0[0]).eq(clearingHouseMock.address)
expect(task0[1]).eq(toFullDigit(1))
const task2 = await KeeperRewardL1.tasksMap("0xf463e18e")
expect(task2[0]).eq(chainlinkL1Mock.address)
expect(task2[1]).eq(toFullDigit(10))
})
it("setKeeperFunction, can set empty contract address", async () => {
await KeeperRewardL1.setKeeperFunctions(
["0x3e09fa10", "0x36405257"],
[clearingHouseMock.address, EMPTY_ADDRESS],
[toFullDigit(1), toFullDigit(5)],
)
const task = await KeeperRewardL1.tasksMap("0x36405257")
expect(task[0]).eq(EMPTY_ADDRESS)
})
it("force error, setKeeperFunction, set 3 func selectors, 3 addresses but 2 amount", async () => {
await expectRevert(
KeeperRewardL1.setKeeperFunctions(
["0x3e09fa10", "0x36405257", "0xf463e18e"],
[clearingHouseMock.address, clearingHouseMock.address, chainlinkL1Mock.address],
[toFullDigit(1), toFullDigit(5)],
),
"inconsistent input size",
)
})
it("force error, setKeeperFunction, set 2 func selectors, 3 addresses but 3 amount", async () => {
await expectRevert(
KeeperRewardL1.setKeeperFunctions(
["0x3e09fa10", "0x36405257"],
[clearingHouseMock.address, clearingHouseMock.address, chainlinkL1Mock.address],
[toFullDigit(1), toFullDigit(5), toFullDigit(5)],
),
"inconsistent input size",
)
})
})
describe("KeeperRewardL1/L2", () => {
beforeEach(async () => {
await perpToken.transfer(KeeperRewardL1.address, toFullDigit(100), { from: admin })
await perpToken.transfer(KeeperRewardL2.address, toFullDigit(100), { from: admin })
// f463e18e => updateLatestRoundData(bytes32)
await KeeperRewardL1.setKeeperFunctions(["0xf463e18e"], [chainlinkL1Mock.address], [toFullDigit(2)])
// 3e09fa10 => payFunding(address)
await KeeperRewardL2.setKeeperFunctions(["0x3e09fa10"], [clearingHouseMock.address], [toFullDigit(1)])
})
it("call payFunding through KeeperRewardL2", async () => {
const r = await KeeperRewardL2.payFunding(fakeAmm, { from: alice })
await expectEvent.inTransaction(r.tx, KeeperRewardL2, "KeeperCalled", {
keeper: alice,
func: "0x3e09fa10",
reward: toFullDigit(1),
})
await expectEvent.inTransaction(r.tx, clearingHouseMock, "TestEventForPayFunding")
expect(await perpToken.balanceOf(alice)).eq(toFullDigit(1))
})
it("call updatePriceFeed through KeeperRewardL1", async () => {
const r = await KeeperRewardL1.updatePriceFeed(fakeAmm, { from: alice })
await expectEvent.inTransaction(r.tx, KeeperRewardL1, "KeeperCalled", {
keeper: alice,
func: "0xf463e18e",
reward: toFullDigit(2),
})
await expectEvent.inTransaction(r.tx, chainlinkL1Mock, "PriceUpdated")
expect(await perpToken.balanceOf(alice)).eq(toFullDigit(2))
})
it("change reward amount and call payFunding through KeeperRewardL2", async () => {
await KeeperRewardL2.setKeeperFunctions(["0x3e09fa10"], [clearingHouseMock.address], [toFullDigit(5)])
const r = await KeeperRewardL2.payFunding(fakeAmm, { from: alice })
await expectEvent.inTransaction(r.tx, KeeperRewardL2, "KeeperCalled", {
keeper: alice,
func: "0x3e09fa10",
reward: toFullDigit(5),
})
await expectEvent.inTransaction(r.tx, clearingHouseMock, "TestEventForPayFunding")
expect(await perpToken.balanceOf(alice)).eq(toFullDigit(5))
})
it("change contract address and call payFunding through KeeperRewardL2", async () => {
const clearingHouseMock2 = await deployClearingHouseMock()
await KeeperRewardL2.setKeeperFunctions(["0x3e09fa10"], [clearingHouseMock2.address], [toFullDigit(5)])
const r = await KeeperRewardL2.payFunding(fakeAmm, { from: alice })
await expectEvent.inTransaction(r.tx, KeeperRewardL2, "KeeperCalled", {
keeper: alice,
func: "0x3e09fa10",
reward: toFullDigit(5),
})
await expectEvent.inTransaction(r.tx, clearingHouseMock2, "TestEventForPayFunding")
expect(await perpToken.balanceOf(alice)).eq(toFullDigit(5))
})
it("force error, set contract address of task `payFunding` to zero in KeeperRewardL2", async () => {
await KeeperRewardL2.setKeeperFunctions(["0x3e09fa10"], [EMPTY_ADDRESS], [toFullDigit(1)])
await expectRevert(KeeperRewardL2.payFunding(fakeAmm), "cannot find contract addr")
})
it("force error, set contract address of task `updatePriceFeed` to zero in KeeperRewardL1", async () => {
await KeeperRewardL1.setKeeperFunctions(["0xf463e18e"], [EMPTY_ADDRESS], [toFullDigit(1)])
await expectRevert(KeeperRewardL1.updatePriceFeed(fakeAmm, { from: alice }), "cannot find contract addr")
})
it("force error, set contract address which doesn't support `payFunding` function in KeeperRewardL2", async () => {
await KeeperRewardL2.setKeeperFunctions(["0x3e09fa10"], [chainlinkL1Mock.address], [toFullDigit(1)])
await expectRevert(
KeeperRewardL2.payFunding(fakeAmm),
"function selector was not recognized and there's no fallback function",
)
})
})
})
Example #16
Source File: L2PriceFeedSpec.ts From perpetual-protocol with GNU General Public License v3.0 | 4 votes |
describe("L2PriceFeed Spec", () => {
let addresses: string[]
let admin: string
let l2PriceFeed!: L2PriceFeedFakeInstance
let ambBridge: AMBBridgeMockInstance
beforeEach(async () => {
addresses = await web3.eth.getAccounts()
admin = addresses[0]
ambBridge = await deployMockAMBBridge()
l2PriceFeed = await deployL2PriceFeed(ambBridge.address, admin)
await ambBridge.mockSetMessageSender(admin)
})
function toBytes32(str: string): string {
const paddingLen = 32 - str.length
const hex = web3.utils.asciiToHex(str)
return hex + "00".repeat(paddingLen)
}
function fromBytes32(str: string): string {
return web3.utils.hexToUtf8(str)
}
describe("addAggregator", () => {
it("addAggregator", async () => {
await l2PriceFeed.addAggregator(toBytes32("ETH"))
expect(fromBytes32(await l2PriceFeed.priceFeedKeys(0))).eq("ETH")
})
it("add multi aggregators", async () => {
await l2PriceFeed.addAggregator(toBytes32("ETH"))
await l2PriceFeed.addAggregator(toBytes32("BTC"))
await l2PriceFeed.addAggregator(toBytes32("LINK"))
expect(fromBytes32(await l2PriceFeed.priceFeedKeys(0))).eq("ETH")
expect(fromBytes32(await l2PriceFeed.priceFeedKeys(2))).eq("LINK")
})
})
describe("removeAggregator", () => {
it("remove 1 aggregator when there's only 1", async () => {
await l2PriceFeed.addAggregator(toBytes32("ETH"))
await l2PriceFeed.removeAggregator(toBytes32("ETH"))
// cant use expectRevert because the error message is different between CI and local env
let error
try {
await l2PriceFeed.priceFeedKeys(0)
} catch (e) {
error = e
}
expect(error).not.eq(undefined)
})
it("remove 1 aggregator when there're 2", async () => {
await l2PriceFeed.addAggregator(toBytes32("ETH"))
await l2PriceFeed.addAggregator(toBytes32("BTC"))
await l2PriceFeed.removeAggregator(toBytes32("ETH"))
expect(fromBytes32(await l2PriceFeed.priceFeedKeys(0))).eq("BTC")
expect(await l2PriceFeed.getPriceFeedLength(toBytes32("ETH"))).eq(0)
})
})
describe("setLatestData/getPrice/getLatestTimestamp", () => {
beforeEach(async () => {
await l2PriceFeed.addAggregator(toBytes32("ETH"))
await l2PriceFeed.mockSetMsgSender(ambBridge.address)
})
it("setLatestData", async () => {
const currentTime = await l2PriceFeed.mock_getCurrentTimestamp()
const dataTimestamp = currentTime.addn(15)
const r = await l2PriceFeed.setLatestData(toBytes32("ETH"), toFullDigit(400), dataTimestamp, 1)
await expectEvent.inTransaction(r.tx, l2PriceFeed, "PriceFeedDataSet", {
key: new BN(toBytes32("ETH")),
price: toFullDigit(400),
timestamp: dataTimestamp,
roundId: "1",
})
expect(await l2PriceFeed.getPriceFeedLength(toBytes32("ETH"))).eq(1)
const price = await l2PriceFeed.getPrice(toBytes32("ETH"))
expect(price).eq(toFullDigit(400))
const timestamp = await l2PriceFeed.getLatestTimestamp(toBytes32("ETH"))
expect(timestamp).eq(dataTimestamp)
})
it("set multiple data", async () => {
const currentTime = await l2PriceFeed.mock_getCurrentTimestamp()
await l2PriceFeed.setLatestData(toBytes32("ETH"), toFullDigit(400), currentTime.addn(15), 100)
await l2PriceFeed.setLatestData(toBytes32("ETH"), toFullDigit(410), currentTime.addn(30), 101)
const r = await l2PriceFeed.setLatestData(toBytes32("ETH"), toFullDigit(420), currentTime.addn(45), 102)
await expectEvent.inTransaction(r.tx, l2PriceFeed, "PriceFeedDataSet")
expect(await l2PriceFeed.getPriceFeedLength(toBytes32("ETH"))).eq(3)
const price = await l2PriceFeed.getPrice(toBytes32("ETH"))
expect(price).eq(toFullDigit(420))
const timestamp = await l2PriceFeed.getLatestTimestamp(toBytes32("ETH"))
expect(timestamp).eq(currentTime.addn(45))
})
it("getPrice after remove the aggregator", async () => {
await l2PriceFeed.mockSetMsgSender(admin)
await l2PriceFeed.addAggregator(toBytes32("BTC"), { from: admin })
const currentTime = await l2PriceFeed.mock_getCurrentTimestamp()
await l2PriceFeed.mockSetMsgSender(ambBridge.address)
await l2PriceFeed.setLatestData(toBytes32("ETH"), toFullDigit(400), currentTime.addn(15), 100)
await l2PriceFeed.setLatestData(toBytes32("ETH"), toFullDigit(410), currentTime.addn(30), 101)
await l2PriceFeed.setLatestData(toBytes32("ETH"), toFullDigit(420), currentTime.addn(45), 102)
await l2PriceFeed.mockSetMsgSender(admin)
await l2PriceFeed.removeAggregator(toBytes32("ETH"))
await expectRevert(l2PriceFeed.getPrice(toBytes32("ETH")), "key not existed")
await expectRevert(l2PriceFeed.getLatestTimestamp(toBytes32("ETH")), "key not existed")
})
it("round id can be the same", async () => {
await l2PriceFeed.setLatestData(toBytes32("ETH"), toFullDigit(400), 1000, 100)
const r = await l2PriceFeed.setLatestData(toBytes32("ETH"), toFullDigit(400), 1001, 100)
await expectEvent.inTransaction(r.tx, l2PriceFeed, "PriceFeedDataSet")
})
it("force error, get data with no price feed data", async () => {
await l2PriceFeed.mockSetMsgSender(admin)
expect(await l2PriceFeed.getPriceFeedLength(toBytes32("ETH"))).eq(0)
expect(await l2PriceFeed.getLatestTimestamp(toBytes32("ETH"))).eq(0)
await expectRevert(l2PriceFeed.getPrice(toBytes32("ETH")), "no price data")
await expectRevert(l2PriceFeed.getTwapPrice(toBytes32("ETH"), 1), "Not enough history")
await expectRevert(l2PriceFeed.getPreviousPrice(toBytes32("ETH"), 0), "Not enough history")
await expectRevert(l2PriceFeed.getPreviousTimestamp(toBytes32("ETH"), 0), "Not enough history")
})
it("force error, aggregator should be set first", async () => {
await expectRevert(
l2PriceFeed.setLatestData(toBytes32("BTC"), toFullDigit(400), 1000, 100),
"key not existed",
)
})
it("force error, timestamp should be larger", async () => {
await l2PriceFeed.setLatestData(toBytes32("ETH"), toFullDigit(400), 1000, 100)
await expectRevert(
l2PriceFeed.setLatestData(toBytes32("ETH"), toFullDigit(400), 999, 101),
"incorrect timestamp",
)
})
it("force error, timestamp can't be the same", async () => {
await l2PriceFeed.setLatestData(toBytes32("ETH"), toFullDigit(400), 1000, 100)
await expectRevert(
l2PriceFeed.setLatestData(toBytes32("ETH"), toFullDigit(400), 1000, 101),
"incorrect timestamp",
)
})
})
describe("twap", () => {
beforeEach(async () => {
await l2PriceFeed.addAggregator(toBytes32("ETH"))
await ambBridge.mockSetMessageSender(admin)
await l2PriceFeed.mockSetMsgSender(ambBridge.address)
const currentTime = await l2PriceFeed.mock_getCurrentTimestamp()
await l2PriceFeed.mock_setBlockTimestamp(currentTime.addn(15))
await l2PriceFeed.setLatestData(toBytes32("ETH"), toFullDigit(400), currentTime.addn(15), 1)
await l2PriceFeed.mock_setBlockTimestamp(currentTime.addn(30))
await l2PriceFeed.setLatestData(toBytes32("ETH"), toFullDigit(405), currentTime.addn(30), 2)
await l2PriceFeed.mock_setBlockTimestamp(currentTime.addn(45))
await l2PriceFeed.setLatestData(toBytes32("ETH"), toFullDigit(410), currentTime.addn(45), 3)
await l2PriceFeed.mock_setBlockTimestamp(currentTime.addn(60))
})
// aggregator's answer
// timestamp(base + 0) : 400
// timestamp(base + 15) : 405
// timestamp(base + 30) : 410
// now = base + 45
//
// --+------+-----+-----+-----+-----+-----+
// base now
it("twap price", async () => {
const price = await l2PriceFeed.getTwapPrice(toBytes32("ETH"), 45)
expect(price).to.eq(toFullDigit(405))
})
it("asking interval more than aggregator has", async () => {
const price = await l2PriceFeed.getTwapPrice(toBytes32("ETH"), 46)
expect(price).to.eq(toFullDigit(405))
})
it("asking interval less than aggregator has", async () => {
const price = await l2PriceFeed.getTwapPrice(toBytes32("ETH"), 44)
expect(price).to.eq("405113636363636363636")
})
it("given variant price period", async () => {
const currentTime = await l2PriceFeed.mock_getCurrentTimestamp()
await l2PriceFeed.mock_setBlockTimestamp(currentTime.addn(30))
await l2PriceFeed.setLatestData(toBytes32("ETH"), toFullDigit(420), currentTime.addn(30), 4)
await l2PriceFeed.mock_setBlockTimestamp(currentTime.addn(50))
// twap price should be (400 * 15) + (405 * 15) + (410 * 45) + (420 * 20) / 95 = 409.74
const price = await l2PriceFeed.getTwapPrice(toBytes32("ETH"), 95)
expect(price).to.eq("409736842105263157894")
})
it("latest price update time is earlier than the request, return the latest price", async () => {
const currentTime = await l2PriceFeed.mock_getCurrentTimestamp()
await l2PriceFeed.mock_setBlockTimestamp(currentTime.addn(100))
// latest update time is base + 30, but now is base + 145 and asking for (now - 45)
// should return the latest price directly
const price = await l2PriceFeed.getTwapPrice(toBytes32("ETH"), 45)
expect(price).to.eq(toFullDigit(410))
})
it("get 0 while interval is zero", async () => {
await expectRevert(l2PriceFeed.getTwapPrice(toBytes32("ETH"), 0), "interval can't be 0")
})
})
describe("getPreviousPrice/getPreviousTimestamp", () => {
let baseTimestamp: BN
beforeEach(async () => {
await l2PriceFeed.addAggregator(toBytes32("ETH"))
await l2PriceFeed.mockSetMsgSender(ambBridge.address)
const currentTime = await l2PriceFeed.mock_getCurrentTimestamp()
baseTimestamp = currentTime
await l2PriceFeed.setLatestData(toBytes32("ETH"), toFullDigit(400), currentTime.addn(15), 1)
await l2PriceFeed.setLatestData(toBytes32("ETH"), toFullDigit(405), currentTime.addn(30), 2)
await l2PriceFeed.setLatestData(toBytes32("ETH"), toFullDigit(410), currentTime.addn(45), 3)
await l2PriceFeed.mock_setBlockTimestamp(currentTime.addn(60))
})
it("get previous price (latest)", async () => {
const price = await l2PriceFeed.getPreviousPrice(toBytes32("ETH"), 0)
expect(price).to.eq(toFullDigit(410))
const timestamp = await l2PriceFeed.getPreviousTimestamp(toBytes32("ETH"), 0)
expect(timestamp).to.eq(baseTimestamp.addn(45))
})
it("get previous price", async () => {
const price = await l2PriceFeed.getPreviousPrice(toBytes32("ETH"), 2)
expect(price).to.eq(toFullDigit(400))
const timestamp = await l2PriceFeed.getPreviousTimestamp(toBytes32("ETH"), 2)
expect(timestamp).to.eq(baseTimestamp.addn(15))
})
it("force error, get previous price", async () => {
await expectRevert(l2PriceFeed.getPreviousPrice(toBytes32("ETH"), 3), "Not enough history")
await expectRevert(l2PriceFeed.getPreviousTimestamp(toBytes32("ETH"), 3), "Not enough history")
})
})
})
Example #17
Source File: FeeRewardPoolL1Spec.ts From perpetual-protocol with GNU General Public License v3.0 | 4 votes |
describe("FeeRewardPoolL1Spec", () => {
let admin: string
let alice: string
let bob: string
let feeTokenPoolDispatcher: string
let stakedPerpToken: StakedPerpTokenMockInstance
let feeRewardPool: FeeRewardPoolL1FakeInstance
let usdt: ERC20FakeInstance
async function forwardBlockTimestamp(time: number): Promise<void> {
const timestamp = await feeRewardPool.mock_getCurrentTimestamp()
const blockNumber = await feeRewardPool.mock_getCurrentBlockNumber()
const movedBlocks = time / 15 < 1 ? 1 : time / 15
await feeRewardPool.mock_setBlockTimestamp(timestamp.addn(time))
await feeRewardPool.mock_setBlockNumber(blockNumber.addn(movedBlocks))
}
beforeEach(async () => {
const addresses = await web3.eth.getAccounts()
admin = addresses[0]
alice = addresses[1]
bob = addresses[2]
feeTokenPoolDispatcher = addresses[3]
usdt = await deployErc20Fake(toFullDigit(2000000))
stakedPerpToken = await deployStakedPerpTokenMock()
feeRewardPool = await deployFeeRewardPoolL1(usdt.address, stakedPerpToken.address, feeTokenPoolDispatcher)
await feeRewardPool.mock_setStakedPerpTokenAddr(admin)
await usdt.transfer(feeRewardPool.address, toFullDigit(20000))
})
describe("notifyRewardAmount()", () => {
beforeEach(async () => {
await stakedPerpToken.mock_setTotalSupply(toFullDigit(ONE_DAY * 10))
})
it("first time calling notifyRewardAmount() when totalSupply is 0, should emit event", async () => {
await stakedPerpToken.mock_setTotalSupply(0)
const timestamp = await feeRewardPool.mock_getCurrentTimestamp()
const periodFinish = timestamp.addn(ONE_DAY)
const rewardAmount = ONE_DAY
const receipt = await feeRewardPool.notifyRewardAmount(toDecimal(rewardAmount), {
from: feeTokenPoolDispatcher,
})
expectEvent.inTransaction(receipt.tx, feeRewardPool, "RewardTransferred", {
amount: toFullDigit(rewardAmount),
})
expect(await feeRewardPool.lastUpdateTime()).to.eq(timestamp)
// rewardRate: 86,400 / 86,400 = 1
expect(await feeRewardPool.rewardRate()).to.eq(toFullDigit(1))
expect(await feeRewardPool.periodFinish()).to.eq(periodFinish)
// rewardPerTokenStored: totalSupply = 0, rewardPerTokenStored = 0, hence = 0
expect(await feeRewardPool.rewardPerTokenStored()).to.eq(0)
})
it("first time calling notifyRewardAmount() when totalSupply is not 0", async () => {
const rewardAmount = ONE_DAY
await feeRewardPool.notifyRewardAmount(toDecimal(rewardAmount), { from: feeTokenPoolDispatcher })
// timeInterval: periodFinish = 0, lastUpdateTime = 0, hence = 0
// rewardPerTokenStored: rewardPerTokenStored = 0, hence 0 + ? * 0 = 0
expect(await feeRewardPool.rewardPerTokenStored()).to.eq(0)
})
it("second time calling notifyRewardAmount(), the calling time == periodFinish", async () => {
await feeRewardPool.notifyRewardAmount(toDecimal(ONE_DAY), { from: feeTokenPoolDispatcher })
const timestamp = await feeRewardPool.mock_getCurrentTimestamp()
await forwardBlockTimestamp(ONE_DAY)
const rewardAmount = ONE_DAY * 2
await feeRewardPool.notifyRewardAmount(toDecimal(rewardAmount), { from: feeTokenPoolDispatcher })
expect(await feeRewardPool.lastUpdateTime()).to.eq(timestamp.addn(ONE_DAY))
expect(await feeRewardPool.rewardRate()).to.eq(toFullDigit(2))
// lastTimeRewardApplicable() = periodFinish == blockTimestamp()
// timeInterval = ONE_DAY, totalSupply = 10 * ONE_DAY
// rewardPerTokenStored = 0 + (1 / (10 * ONE_DAY)) * ONE_DAY ~= 0.1
expect(await feeRewardPool.rewardPerTokenStored()).to.eq("99999999999964800")
})
it("second time calling notifyRewardAmount(), the calling time > periodFinish", async () => {
await feeRewardPool.notifyRewardAmount(toDecimal(ONE_DAY), { from: feeTokenPoolDispatcher })
const timestamp = await feeRewardPool.mock_getCurrentTimestamp()
await forwardBlockTimestamp(ONE_DAY + 1)
const rewardAmount = ONE_DAY * 2
await feeRewardPool.notifyRewardAmount(toDecimal(rewardAmount), { from: feeTokenPoolDispatcher })
expect(await feeRewardPool.lastUpdateTime()).to.eq(timestamp.addn(ONE_DAY + 1))
expect(await feeRewardPool.rewardRate()).to.eq(toFullDigit(2))
// lastTimeRewardApplicable() = periodFinish
// timeInterval = ONE_DAY, totalSupply = 10 * ONE_DAY
// rewardPerTokenStored = 0 + (1 / (10 * ONE_DAY)) * ONE_DAY ~= 0.1
expect(await feeRewardPool.rewardPerTokenStored()).to.eq("99999999999964800")
})
it("second time calling notifyRewardAmount(), the calling time < periodFinish", async () => {
// rewardRate: 86,400 / 86,400 = 1
await feeRewardPool.notifyRewardAmount(toDecimal(ONE_DAY), { from: feeTokenPoolDispatcher })
const timestamp = await feeRewardPool.mock_getCurrentTimestamp()
await forwardBlockTimestamp(ONE_DAY / 4)
const rewardAmount = ONE_DAY * 2
await feeRewardPool.notifyRewardAmount(toDecimal(rewardAmount), { from: feeTokenPoolDispatcher })
expect(await feeRewardPool.lastUpdateTime()).to.eq(timestamp.addn(ONE_DAY / 4))
// remainingTime: ONE_DAY - ONE_DAY / 4
// leftover: 1 * (ONE_DAY * 3 / 4)
// rewardRate: (ONE_DAY * 2 + (ONE_DAY * 3 / 4) ) / ONE_DAY = 2.75
expect(await feeRewardPool.rewardRate()).to.eq(toFullDigit(2.75))
// timeInterval = ONE_DAY / 4
// totalSupply = 10 * ONE_DAY
// rewardPerTokenStored = 0 + (1 / (10 * ONE_DAY)) * (ONE_DAY / 4) ~= 0.025
expect(await feeRewardPool.rewardPerTokenStored()).to.eq("24999999999991200")
})
it("force error, not called by feeTokenPoolDispatcher", async () => {
await expectRevert(
feeRewardPool.notifyRewardAmount(toDecimal(100), { from: alice }),
"only feeTokenPoolDispatcher",
)
})
it("force error, token amount is zero", async () => {
await expectRevert(
feeRewardPool.notifyRewardAmount(toDecimal(0), { from: feeTokenPoolDispatcher }),
"invalid input",
)
})
})
describe("notifyStakeChanged()", () => {
beforeEach(async () => {
await stakedPerpToken.mock_setTotalSupply(toFullDigit(10 * ONE_DAY))
await feeRewardPool.notifyRewardAmount(toDecimal(ONE_DAY), { from: feeTokenPoolDispatcher })
// rewardRate: ONE_DAY / ONE_DAY = 1
})
it("alice stakes 10% of the total sPerp", async () => {
await forwardBlockTimestamp(ONE_DAY / 4)
await stakedPerpToken.mock_setBalance(alice, toFullDigit(ONE_DAY))
await feeRewardPool.notifyStakeChanged(alice)
// timeInterval = ONE_DAY / 4
// totalSupply = 10 * ONE_DAY
// rewardRate = 1
// rewardPerTokenStored = 0 + (1 / (10 * ONE_DAY)) * (ONE_DAY / 4) ~= 0.025
expect(await feeRewardPool.rewardPerTokenStored()).to.eq("24999999999991200")
// balance: ONE_DAY
// rewardPerTokenStored ~= 0.025
// userRewardPerTokenPaid(staker) = 0 (during the calculation of rewards, this is not yet modified)
// rewards(staker) = 0
// ONE_DAY * 0.025 ~= 21600
expect(await feeRewardPool.rewards(alice)).to.eq("2159999999999239680000")
expect(await feeRewardPool.userRewardPerTokenPaid(alice)).to.eq("24999999999991200")
})
it("alice stakes 10% & bob stakes 20% at the same period and the same time", async () => {
await forwardBlockTimestamp(ONE_DAY / 4)
await stakedPerpToken.mock_setBalance(alice, toFullDigit(ONE_DAY))
await stakedPerpToken.mock_setBalance(bob, toFullDigit(ONE_DAY * 2))
await feeRewardPool.notifyStakeChanged(alice)
await feeRewardPool.notifyStakeChanged(bob)
// notifies alice's stake & bob's stake are the same:
// timeInterval = ONE_DAY / 4
// totalSupply = 10 * ONE_DAY
// rewardRate = 1
// rewardPerTokenStored = 0 + (1 / (10 * ONE_DAY)) * (ONE_DAY / 4) ~= 0.025
expect(await feeRewardPool.rewardPerTokenStored()).to.eq("24999999999991200")
// alice's rewards:
// balance: ONE_DAY
// rewardPerTokenStored ~= 0.025
// userRewardPerTokenPaid(staker) = 0
// rewards(staker) = 0
// ONE_DAY * 0.025 ~= 2160
// bob's rewards:
// balance: ONE_DAY * 2
// the rest is the same as the above
expect(await feeRewardPool.rewards(alice)).to.eq("2159999999999239680000")
expect(await feeRewardPool.rewards(bob)).to.eq("4319999999998479360000")
// userRewardPerTokenPaid(staker) = rewardPerTokenStored
expect(await feeRewardPool.userRewardPerTokenPaid(alice)).to.eq("24999999999991200")
expect(await feeRewardPool.userRewardPerTokenPaid(bob)).to.eq("24999999999991200")
})
it("alice stakes 10% & bob stakes 20% at the same period but not exactly the same time", async () => {
await forwardBlockTimestamp(ONE_DAY / 4)
await stakedPerpToken.mock_setBalance(alice, toFullDigit(ONE_DAY))
await feeRewardPool.notifyStakeChanged(alice)
await forwardBlockTimestamp(ONE_DAY / 4)
await stakedPerpToken.mock_setBalance(bob, toFullDigit(ONE_DAY * 2))
await feeRewardPool.notifyStakeChanged(bob)
// timeInterval = ONE_DAY / 2
// totalSupply = 10 * ONE_DAY
// rewardRate = 1
// rewardPerTokenStored = 0 + (1 / (10 * ONE_DAY)) * (ONE_DAY / 2) ~= 0.05
expect(await feeRewardPool.rewardPerTokenStored()).to.eq("49999999999982400")
// alice's balances remain the same as the above case
expect(await feeRewardPool.rewards(alice)).to.eq("2159999999999239680000")
expect(await feeRewardPool.userRewardPerTokenPaid(alice)).to.eq("24999999999991200")
// bob's rewards:
// balance: ONE_DAY * 2
// rewardPerTokenStored ~= 0.05
// userRewardPerTokenPaid(staker) = 0
// rewards(staker) = 0
// ONE_DAY * 0.025 ~= 2160
expect(await feeRewardPool.rewards(bob)).to.eq("8639999999996958720000")
expect(await feeRewardPool.userRewardPerTokenPaid(bob)).to.eq("49999999999982400")
})
})
describe("notifyRewardAmount() & notifyStakeChanged() in multiple periods", () => {
beforeEach(async () => {
await stakedPerpToken.mock_setTotalSupply(toFullDigit(10 * ONE_DAY))
await feeRewardPool.notifyRewardAmount(toDecimal(ONE_DAY), { from: feeTokenPoolDispatcher })
// lastUpdateTime = original value
// alice stakes 10%
await forwardBlockTimestamp(ONE_DAY / 4)
await stakedPerpToken.mock_setBalance(alice, toFullDigit(ONE_DAY))
await feeRewardPool.notifyStakeChanged(alice)
// rewards(alice) ~= 2160
// rewardPerTokenStored ~= 0.025
// lastUpdateTime = original value + ONE_DAY / 4
})
it("alice stakes 10% twice in two periods", async () => {
await forwardBlockTimestamp((ONE_DAY * 3) / 4)
await feeRewardPool.notifyRewardAmount(toDecimal(ONE_DAY * 2), { from: feeTokenPoolDispatcher })
// rewardRate = 2
// lastUpdateTime = original value + ONE_DAY / 4
// timeInterval = ONE_DAY * 3 / 4
// totalSupply = 10 * ONE_DAY
// rewardRate = 1 (during the calculation this value is not changed yet)
// rewardPerTokenStored ~= 0.025 + (1 / 10 * ONE_DAY) * 3 * ONE_DAY / 4 ~= 0.1
expect(await feeRewardPool.rewardPerTokenStored()).to.eq("99999999999964800")
await forwardBlockTimestamp(ONE_DAY / 5)
await stakedPerpToken.mock_setBalance(alice, toFullDigit(ONE_DAY * 2))
await feeRewardPool.notifyStakeChanged(alice)
// timeInterval = ONE_DAY / 5
// totalSupply = 10 * ONE_DAY
// rewardRate = 2
// rewardPerTokenStored ~= 0.1 + (2 / 10 * ONE_DAY) * ONE_DAY / 5 ~= 0.14
expect(await feeRewardPool.rewardPerTokenStored()).to.eq("139999999999950720")
// balance: ONE_DAY * 2
// rewardPerTokenStored ~= 0.14
// userRewardPerTokenPaid(staker) ~= 0.025
// rewards(staker) ~= 2160
// ONE_DAY * 2 * (0.14 - 0.025) + 2160 ~= 22032
expect(await feeRewardPool.rewards(alice)).to.eq("22031999999992244736000")
expect(await feeRewardPool.userRewardPerTokenPaid(alice)).to.eq("139999999999950720")
})
// TODO
it.skip("alice stakes 10% & bob stakes 20% at one period of the same time, and then alice stakes 10% again in the next period", async () => {
await stakedPerpToken.mock_setBalance(bob, toFullDigit(ONE_DAY * 2))
await feeRewardPool.notifyStakeChanged(bob)
await forwardBlockTimestamp((ONE_DAY * 3) / 4)
await feeRewardPool.notifyRewardAmount(toDecimal(ONE_DAY * 2), { from: feeTokenPoolDispatcher })
// rewardRate: ONE_DAY * 2 / ONE_DAY = 2
await forwardBlockTimestamp(ONE_DAY / 5)
await stakedPerpToken.mock_setBalance(alice, toFullDigit(ONE_DAY * 2))
await feeRewardPool.notifyStakeChanged(alice)
})
})
describe("withdrawReward()", () => {
beforeEach(async () => {
await stakedPerpToken.mock_setTotalSupply(toFullDigit(10 * ONE_DAY))
await feeRewardPool.notifyRewardAmount(toDecimal(ONE_DAY), { from: feeTokenPoolDispatcher })
})
it("alice withdraws reward right after notifyStakeChanged()", async () => {
await forwardBlockTimestamp(ONE_DAY / 4)
await stakedPerpToken.mock_setBalance(alice, toFullDigit(ONE_DAY))
await feeRewardPool.notifyStakeChanged(alice)
// rewards(alice) ~= 2160
// rewardPerTokenStored ~= 0.025
// lastUpdateTime = original value + ONE_DAY / 4
const receipt = await feeRewardPool.withdrawReward({ from: alice })
expectEvent.inTransaction(receipt.tx, feeRewardPool, "RewardWithdrawn", {
staker: alice,
amount: "2159999999999239680000",
})
// lastUpdateTime = original value + ONE_DAY / 4
expect(await feeRewardPool.rewards(alice)).to.eq(0)
expect(await usdt.balanceOf(alice)).to.eq("2159999999999239680000")
// timeInterval = 0
// rewardPerTokenStored ~= 0.025 + 0 ~= 0.025
expect(await feeRewardPool.rewardPerTokenStored()).to.eq("24999999999991200")
expect(await feeRewardPool.userRewardPerTokenPaid(alice)).to.eq("24999999999991200")
})
// TODO
it.skip("alice & bob withdraw reward of different amount in the same period as staking but not exactly the same time", async () => {
await forwardBlockTimestamp(ONE_DAY / 4)
await stakedPerpToken.mock_setBalance(alice, toFullDigit(ONE_DAY))
await feeRewardPool.notifyStakeChanged(alice)
await forwardBlockTimestamp(ONE_DAY / 4)
await stakedPerpToken.mock_setBalance(bob, toFullDigit(ONE_DAY * 2))
await feeRewardPool.notifyStakeChanged(bob)
await forwardBlockTimestamp(ONE_DAY / 4)
})
it("force error, rewards is 0", async () => {
await expectRevert(feeRewardPool.withdrawReward({ from: alice }), "reward is 0")
})
})
})
Example #18
Source File: PerpRewardVestingSpec.ts From perpetual-protocol with GNU General Public License v3.0 | 4 votes |
describe("PerpRewardVestingSpec", () => {
const RANDOM_BYTES32_1 = "0x7c1b1e7c2eaddafdf52250cba9679e5b30014a9d86a0e2af17ec4cee24a5fc80"
const RANDOM_BYTES32_2 = "0xb6801f31f93d990dfe65d67d3479c3853d5fafd7a7f2b8fad9e68084d8d409e0"
const RANDOM_BYTES32_3 = "0x43bd90E4CC93D6E40580507102Cc7B1Bc8A25284a7f2b8fad9e68084d8d409e0"
let admin: string
let alice: string
let bob: string
let perpRewardVesting: PerpRewardVestingFakeInstance
let perpToken: PerpTokenInstance
let vestingPeriod: BN
async function forwardBlockTimestamp(time: number): Promise<void> {
const timestamp = await perpRewardVesting.mock_getCurrentTimestamp()
const blockNumber = await perpRewardVesting.mock_getCurrentBlockNumber()
const movedBlocks = time / 15 < 1 ? 1 : time / 15
await perpRewardVesting.mock_setBlockTimestamp(timestamp.addn(time))
await perpRewardVesting.mock_setBlockNumber(blockNumber.addn(movedBlocks))
}
beforeEach(async () => {
const addresses = await web3.eth.getAccounts()
admin = addresses[0]
alice = addresses[1]
bob = addresses[2]
perpToken = await deployPerpToken(toFullDigit(2000000))
perpRewardVesting = await deployPerpRewardVesting(perpToken.address)
// 12 * 7 * 24 * 60 * 60 = 7,257,600
vestingPeriod = await perpRewardVesting.vestingPeriod()
await perpToken.approve(perpRewardVesting.address, toFullDigit(2000000), { from: admin })
})
describe("seedAllocations()", () => {
it("verify balances after seeding", async () => {
const timestamp = (await perpRewardVesting.mock_getCurrentTimestamp()).addn(86400 * 7 * 12)
await perpRewardVesting.seedAllocations(new BN(1), RANDOM_BYTES32_1, toFullDigit(1000000), { from: admin })
expect(await perpToken.balanceOf(admin)).to.eq(toFullDigit(1000000))
expect(await perpToken.balanceOf(perpRewardVesting.address)).to.eq(toFullDigit(1000000))
expect(await perpRewardVesting.weekMerkleRoots(new BN(1))).to.eq(RANDOM_BYTES32_1)
expect(await perpRewardVesting.merkleRootTimestampMap(new BN(1))).to.eq(timestamp)
expect(await perpRewardVesting.merkleRootIndexes(new BN(0))).to.eq(new BN(1))
})
})
describe("claimWeek()", () => {
it("alice claims her own share", async () => {
await perpRewardVesting.seedAllocations(new BN(1), RANDOM_BYTES32_1, toFullDigit(1000000), { from: admin })
await forwardBlockTimestamp(Number(vestingPeriod))
await perpRewardVesting.claimWeek(alice, new BN(1), toFullDigit(500000), [RANDOM_BYTES32_1], {
from: alice,
})
expect(await perpToken.balanceOf(alice)).to.eq(toFullDigit(500000))
expect(await perpToken.balanceOf(perpRewardVesting.address)).to.eq(toFullDigit(500000))
expect(await perpRewardVesting.claimed(new BN(1), alice)).to.eq(true)
})
it("admin claims alice's share", async () => {
await perpRewardVesting.seedAllocations(new BN(1), RANDOM_BYTES32_1, toFullDigit(1000000), { from: admin })
await forwardBlockTimestamp(Number(vestingPeriod))
await perpRewardVesting.claimWeek(alice, new BN(1), toFullDigit(500000), [RANDOM_BYTES32_1], {
from: admin,
})
expect(await perpToken.balanceOf(alice)).to.eq(toFullDigit(500000))
expect(await perpToken.balanceOf(perpRewardVesting.address)).to.eq(toFullDigit(500000))
expect(await perpRewardVesting.claimed(new BN(1), alice)).to.eq(true)
})
it("alice & bob both claim their shares", async () => {
await perpRewardVesting.seedAllocations(new BN(1), RANDOM_BYTES32_1, toFullDigit(1000000), { from: admin })
await forwardBlockTimestamp(Number(vestingPeriod))
await perpRewardVesting.claimWeek(alice, new BN(1), toFullDigit(500000), [RANDOM_BYTES32_1], {
from: alice,
})
await perpRewardVesting.claimWeek(bob, new BN(1), toFullDigit(300000), [RANDOM_BYTES32_1], {
from: bob,
})
expect(await perpToken.balanceOf(alice)).to.eq(toFullDigit(500000))
expect(await perpToken.balanceOf(bob)).to.eq(toFullDigit(300000))
expect(await perpToken.balanceOf(perpRewardVesting.address)).to.eq(toFullDigit(200000))
expect(await perpRewardVesting.claimed(new BN(1), alice)).to.eq(true)
expect(await perpRewardVesting.claimed(new BN(1), bob)).to.eq(true)
})
it("there are three allocations and alice claims two of them", async () => {
await perpRewardVesting.seedAllocations(new BN(1), RANDOM_BYTES32_1, toFullDigit(500000), { from: admin })
await forwardBlockTimestamp(30)
await perpRewardVesting.seedAllocations(new BN(3), RANDOM_BYTES32_1, toFullDigit(600000), { from: admin })
await forwardBlockTimestamp(30)
await perpRewardVesting.seedAllocations(new BN(5), RANDOM_BYTES32_3, toFullDigit(700000), { from: admin })
await forwardBlockTimestamp(Number(vestingPeriod))
await perpRewardVesting.claimWeek(alice, new BN(1), toFullDigit(500000), [RANDOM_BYTES32_1], {
from: alice,
})
await perpRewardVesting.claimWeek(alice, new BN(5), toFullDigit(700000), [RANDOM_BYTES32_3], {
from: alice,
})
expect(await perpToken.balanceOf(alice)).to.eq(toFullDigit(1200000))
expect(await perpRewardVesting.claimed(new BN(3), alice)).to.eq(false)
await perpRewardVesting.claimWeek(alice, new BN(3), toFullDigit(600000), [RANDOM_BYTES32_2], {
from: alice,
})
expect(await perpToken.balanceOf(alice)).to.eq(toFullDigit(1800000))
})
it("force error, invalid claim, claim not yet available", async () => {
await perpRewardVesting.seedAllocations(new BN(1), RANDOM_BYTES32_1, toFullDigit(1000000), { from: admin })
await forwardBlockTimestamp(60)
await expectRevert(
perpRewardVesting.claimWeek(alice, new BN(1), toFullDigit(500000), [RANDOM_BYTES32_1], {
from: alice,
}),
"Invalid claim",
)
})
it("force error, invalid claim, input week is invalid", async () => {
await perpRewardVesting.seedAllocations(new BN(1), RANDOM_BYTES32_1, toFullDigit(1000000), { from: admin })
await forwardBlockTimestamp(Number(vestingPeriod))
await expectRevert(
perpRewardVesting.claimWeek(alice, new BN(0), toFullDigit(500000), [RANDOM_BYTES32_1], {
from: alice,
}),
"Invalid claim",
)
})
it("force error, claiming twice", async () => {
await perpRewardVesting.seedAllocations(new BN(1), RANDOM_BYTES32_1, toFullDigit(1000000), { from: admin })
await forwardBlockTimestamp(Number(vestingPeriod))
await perpRewardVesting.claimWeek(alice, new BN(1), toFullDigit(500000), [RANDOM_BYTES32_1], {
from: alice,
})
await expectRevert(
perpRewardVesting.claimWeek(alice, new BN(1), toFullDigit(500000), [RANDOM_BYTES32_1], {
from: alice,
}),
"Claimed already",
)
})
// we do not verify if the claimed amount is valid or not; we suppose this is verified by MerkleRedeemUpgradeSafe.sol
it.skip("force error, claimed amount larger than the available quota", async () => {})
})
describe("claimWeeks()", () => {
// when testing claimWeeks(), input all inputs as strings s.t. Claims[] will not cause error
it("alice claims her two shares", async () => {
await perpRewardVesting.seedAllocations(new BN(2), RANDOM_BYTES32_1, toFullDigit(500000), { from: admin })
await forwardBlockTimestamp(30)
await perpRewardVesting.seedAllocations(new BN(7), RANDOM_BYTES32_2, toFullDigit(1000000), { from: admin })
await forwardBlockTimestamp(Number(vestingPeriod))
const claimsArr = [
{
week: "2",
balance: toFullDigitStr(100000),
merkleProof: [RANDOM_BYTES32_1],
},
{
week: "7",
balance: toFullDigitStr(100000),
merkleProof: [RANDOM_BYTES32_2],
},
]
await perpRewardVesting.claimWeeks(alice, claimsArr, { from: alice })
expect(await perpToken.balanceOf(alice)).to.eq(toFullDigit(200000))
expect(await perpToken.balanceOf(perpRewardVesting.address)).to.eq(toFullDigit(1300000))
expect(await perpRewardVesting.claimed("2", alice)).to.eq(true)
expect(await perpRewardVesting.claimed("7", alice)).to.eq(true)
})
it("admin claims alice's two shares", async () => {
await perpRewardVesting.seedAllocations(new BN(2), RANDOM_BYTES32_1, toFullDigit(500000), { from: admin })
await forwardBlockTimestamp(30)
await perpRewardVesting.seedAllocations(new BN(7), RANDOM_BYTES32_2, toFullDigit(1000000), { from: admin })
await forwardBlockTimestamp(Number(vestingPeriod))
const claimsArr = [
{
week: "2",
balance: toFullDigitStr(100000),
merkleProof: [RANDOM_BYTES32_1],
},
{
week: "7",
balance: toFullDigitStr(100000),
merkleProof: [RANDOM_BYTES32_2],
},
]
await perpRewardVesting.claimWeeks(alice, claimsArr, { from: admin })
expect(await perpToken.balanceOf(alice)).to.eq(toFullDigit(200000))
expect(await perpRewardVesting.claimed("2", alice)).to.eq(true)
expect(await perpRewardVesting.claimed("7", alice)).to.eq(true)
})
it("alice & bob both claim their three shares", async () => {
await perpRewardVesting.seedAllocations(new BN(2), RANDOM_BYTES32_1, toFullDigit(500000), { from: admin })
await forwardBlockTimestamp(30)
await perpRewardVesting.seedAllocations(new BN(5), RANDOM_BYTES32_2, toFullDigit(500000), { from: admin })
await forwardBlockTimestamp(30)
await perpRewardVesting.seedAllocations(new BN(7), RANDOM_BYTES32_3, toFullDigit(1000000), { from: admin })
await forwardBlockTimestamp(Number(vestingPeriod))
const claimsArr = [
{
week: "2",
balance: toFullDigitStr(100000),
merkleProof: [RANDOM_BYTES32_1],
},
{
week: "5",
balance: toFullDigitStr(150000),
merkleProof: [RANDOM_BYTES32_2],
},
{
week: "7",
balance: toFullDigitStr(200000),
merkleProof: [RANDOM_BYTES32_3],
},
]
await perpRewardVesting.claimWeeks(alice, claimsArr, { from: alice })
await perpRewardVesting.claimWeeks(bob, claimsArr, { from: bob })
expect(await perpToken.balanceOf(alice)).to.eq(toFullDigit(450000))
expect(await perpToken.balanceOf(bob)).to.eq(toFullDigit(450000))
expect(await perpToken.balanceOf(perpRewardVesting.address)).to.eq(toFullDigit(1100000))
})
it("alice & bob both claim two of their three shares", async () => {
await perpRewardVesting.seedAllocations(new BN(2), RANDOM_BYTES32_1, toFullDigit(500000), { from: admin })
await forwardBlockTimestamp(30)
await perpRewardVesting.seedAllocations(new BN(5), RANDOM_BYTES32_1, toFullDigit(500000), { from: admin })
await forwardBlockTimestamp(30)
await perpRewardVesting.seedAllocations(new BN(7), RANDOM_BYTES32_3, toFullDigit(1000000), { from: admin })
await forwardBlockTimestamp(Number(vestingPeriod))
const aliceClaimsArr = [
{
week: "2",
balance: toFullDigitStr(100000),
merkleProof: [RANDOM_BYTES32_1],
},
{
week: "5",
balance: toFullDigitStr(150000),
merkleProof: [RANDOM_BYTES32_2],
},
]
const bobClaimsArr = [
{
week: "2",
balance: toFullDigitStr(100000),
merkleProof: [RANDOM_BYTES32_1],
},
{
week: "7",
balance: toFullDigitStr(200000),
merkleProof: [RANDOM_BYTES32_3],
},
]
await perpRewardVesting.claimWeeks(alice, aliceClaimsArr, { from: alice })
await perpRewardVesting.claimWeeks(bob, bobClaimsArr, { from: bob })
expect(await perpToken.balanceOf(alice)).to.eq(toFullDigit(250000))
expect(await perpToken.balanceOf(bob)).to.eq(toFullDigit(300000))
expect(await perpToken.balanceOf(perpRewardVesting.address)).to.eq(toFullDigit(1450000))
expect(await perpRewardVesting.claimed("7", alice)).to.eq(false)
expect(await perpRewardVesting.claimed("5", bob)).to.eq(false)
})
it("force error, alice has two shares and both are not yet available", async () => {
await perpRewardVesting.seedAllocations(new BN(2), RANDOM_BYTES32_1, toFullDigit(1000000), { from: admin })
await forwardBlockTimestamp(60)
await perpRewardVesting.seedAllocations(new BN(7), RANDOM_BYTES32_2, toFullDigit(1000000), { from: admin })
await forwardBlockTimestamp(60)
const claimsArr = [
{
week: "2",
balance: toFullDigitStr(100000),
merkleProof: [RANDOM_BYTES32_1],
},
{
week: "7",
balance: toFullDigitStr(100000),
merkleProof: [RANDOM_BYTES32_2],
},
]
await expectRevert(perpRewardVesting.claimWeeks(alice, claimsArr, { from: alice }), "Invalid claim")
})
it("force error, alice has three shares and the latest one is not yet available", async () => {
await perpRewardVesting.seedAllocations(new BN(2), RANDOM_BYTES32_1, toFullDigit(500000), { from: admin })
await forwardBlockTimestamp(30)
await perpRewardVesting.seedAllocations(new BN(5), RANDOM_BYTES32_2, toFullDigit(500000), { from: admin })
await forwardBlockTimestamp(Number(vestingPeriod))
await perpRewardVesting.seedAllocations(new BN(7), RANDOM_BYTES32_3, toFullDigit(1000000), { from: admin })
await forwardBlockTimestamp(30)
const claimsArr = [
{
week: "2",
balance: toFullDigitStr(100000),
merkleProof: [RANDOM_BYTES32_1],
},
{
week: "5",
balance: toFullDigitStr(150000),
merkleProof: [RANDOM_BYTES32_2],
},
{
week: "7",
balance: toFullDigitStr(200000),
merkleProof: [RANDOM_BYTES32_3],
},
]
await expectRevert(perpRewardVesting.claimWeeks(alice, claimsArr, { from: alice }), "Invalid claim")
})
it("force error, claimWeeks() twice", async () => {
await perpRewardVesting.seedAllocations(new BN(2), RANDOM_BYTES32_1, toFullDigit(500000), { from: admin })
await forwardBlockTimestamp(30)
await perpRewardVesting.seedAllocations(new BN(7), RANDOM_BYTES32_2, toFullDigit(1000000), { from: admin })
await forwardBlockTimestamp(Number(vestingPeriod))
const claimsArr = [
{
week: "2",
balance: toFullDigitStr(100000),
merkleProof: [RANDOM_BYTES32_1],
},
{
week: "7",
balance: toFullDigitStr(100000),
merkleProof: [RANDOM_BYTES32_2],
},
]
await perpRewardVesting.claimWeeks(alice, claimsArr, { from: alice })
await expectRevert(perpRewardVesting.claimWeeks(alice, claimsArr, { from: alice }), "Claimed already")
})
it("force error, claiming twice, first claimWeek() then claimWeeks()", async () => {
await perpRewardVesting.seedAllocations(new BN(2), RANDOM_BYTES32_1, toFullDigit(500000), { from: admin })
await forwardBlockTimestamp(30)
await perpRewardVesting.seedAllocations(new BN(7), RANDOM_BYTES32_2, toFullDigit(1000000), { from: admin })
await forwardBlockTimestamp(Number(vestingPeriod))
await perpRewardVesting.claimWeek(alice, new BN(2), toFullDigit(100000), [RANDOM_BYTES32_1], {
from: alice,
})
const claimsArr = [
{
week: "2",
balance: toFullDigitStr(100000),
merkleProof: [RANDOM_BYTES32_1],
},
{
week: "6",
balance: toFullDigitStr(100000),
merkleProof: [RANDOM_BYTES32_2],
},
]
await expectRevert(perpRewardVesting.claimWeeks(alice, claimsArr, { from: alice }), "Claimed already")
})
it("force error, claiming twice, first claimWeeks() then claimWeek()", async () => {
await perpRewardVesting.seedAllocations(new BN(2), RANDOM_BYTES32_1, toFullDigit(500000), { from: admin })
await forwardBlockTimestamp(30)
await perpRewardVesting.seedAllocations(new BN(7), RANDOM_BYTES32_2, toFullDigit(1000000), { from: admin })
await forwardBlockTimestamp(Number(vestingPeriod))
const claimsArr = [
{
week: "2",
balance: toFullDigitStr(100000),
merkleProof: [RANDOM_BYTES32_1],
},
{
week: "7",
balance: toFullDigitStr(100000),
merkleProof: [RANDOM_BYTES32_2],
},
]
await perpRewardVesting.claimWeeks(alice, claimsArr, { from: alice })
await expectRevert(
perpRewardVesting.claimWeek(alice, new BN(2), toFullDigit(100000), [RANDOM_BYTES32_1], {
from: alice,
}),
"Claimed already",
)
})
})
})
Example #19
Source File: RewardsDistributionSpec.ts From perpetual-protocol with GNU General Public License v3.0 | 4 votes |
// skip, won't be in v1
describe.skip("RewardsDistributionSpec", () => {
let accounts: string[]
let rewardsDistribution: RewardsDistributionInstance
beforeEach(async () => {
accounts = await web3.eth.getAccounts()
rewardsDistribution = await deployRewardsDistribution(accounts[1], accounts[2])
})
describe("addRewardsDistribution", () => {
it("add by admin", async () => {
await rewardsDistribution.addRewardsDistribution(accounts[5], toDecimal(100))
await rewardsDistribution.addRewardsDistribution(accounts[6], toDecimal(200))
const recipient0 = await rewardsDistribution.distributions(0)
expect(recipient0[0]).eq(accounts[5])
expect(recipient0[1]).eq(toFullDigit(100))
const recipient1 = await rewardsDistribution.distributions(1)
expect(recipient1[0]).eq(accounts[6])
expect(recipient1[1]).eq(toFullDigit(200))
})
})
describe("editRewardsDistribution", () => {
it("edit by admin", async () => {
await rewardsDistribution.addRewardsDistribution(accounts[5], toDecimal(100))
await rewardsDistribution.editRewardsDistribution(0, accounts[6], toDecimal(200))
const recipient = await rewardsDistribution.distributions(0)
expect(recipient[0]).eq(accounts[6])
expect(recipient[1]).eq(toFullDigit(200))
})
// expectRevert section
it("force error, the length of distribution is still 0", async () => {
await expectRevert(
rewardsDistribution.editRewardsDistribution(0, accounts[5], toDecimal(200)),
"index out of bounds",
)
})
it("force error, the index exceeds the current length", async () => {
await rewardsDistribution.addRewardsDistribution(accounts[5], toDecimal(100))
await expectRevert(
rewardsDistribution.editRewardsDistribution(1, accounts[5], toDecimal(200)),
"index out of bounds",
)
})
})
describe("removeRewardsDistribution", () => {
it("remove by admin", async () => {
await rewardsDistribution.addRewardsDistribution(accounts[5], toDecimal(100))
await rewardsDistribution.addRewardsDistribution(accounts[6], toDecimal(200))
await rewardsDistribution.removeRewardsDistribution(0)
const recipient0 = await rewardsDistribution.distributions(0)
expect(recipient0[0]).eq(accounts[6])
expect(recipient0[1]).eq(toFullDigit(200))
let error
try {
await rewardsDistribution.distributions(1)
} catch (e) {
error = e
}
expect(error).to.exist
})
// expectRevert section
it("force error, the length of distribution is still 0", async () => {
await expectRevert(rewardsDistribution.removeRewardsDistribution(0), "index out of bounds")
})
it("force error, the index exceeds the current length", async () => {
await rewardsDistribution.addRewardsDistribution(accounts[5], toDecimal(100))
await expectRevert(rewardsDistribution.removeRewardsDistribution(1), "index out of bounds")
})
})
})
Example #20
Source File: StakedPerpTokenSpec.ts From perpetual-protocol with GNU General Public License v3.0 | 4 votes |
describe("StakedPerpTokenSpec", () => {
let admin: string
let alice: string
let stakedPerpToken: StakedPerpTokenFakeInstance
let perpToken: PerpTokenInstance
let feeRewardPoolMock1: FeeRewardPoolMockInstance
let feeRewardPoolMock2: FeeRewardPoolMockInstance
let cooldownPeriod: number
async function forwardBlockTimestamp(time: number): Promise<void> {
const timestamp = await stakedPerpToken.mock_getCurrentTimestamp()
const blockNumber = await stakedPerpToken.mock_getCurrentBlockNumber()
const movedBlocks = time / 15 < 1 ? 1 : time / 15
await stakedPerpToken.mock_setBlockTimestamp(timestamp.addn(time))
await stakedPerpToken.mock_setBlockNumber(blockNumber.addn(movedBlocks))
}
beforeEach(async () => {
const addresses = await web3.eth.getAccounts()
admin = addresses[0]
alice = addresses[1]
perpToken = await deployPerpToken(toFullDigit(2000000))
feeRewardPoolMock1 = await deployFeeRewardPoolMock()
feeRewardPoolMock2 = await deployFeeRewardPoolMock()
stakedPerpToken = await deployStakedPerpToken(perpToken.address, new BN(60 * 60 * 24 * 7))
await stakedPerpToken.addStakeModule(feeRewardPoolMock1.address)
await perpToken.transfer(alice, toFullDigit(2000))
await perpToken.approve(stakedPerpToken.address, toFullDigit(2000), { from: alice })
await perpToken.approve(stakedPerpToken.address, toFullDigit(5000), { from: admin })
cooldownPeriod = (await stakedPerpToken.cooldownPeriod()).toNumber()
})
describe("assert ERC20 config", () => {
it("check name, symbol & decimals", async () => {
expect(await stakedPerpToken.name()).to.eq("Staked Perpetual")
expect(await stakedPerpToken.symbol()).to.eq("sPERP")
expect(await stakedPerpToken.decimals()).to.eq(18)
})
})
describe("stake()", () => {
it("alice stakes 100; her balance of sPerp should increase and event be fired", async () => {
const blockNumber = await stakedPerpToken.mock_getCurrentBlockNumber()
const receipt = await stakedPerpToken.stake(toDecimal(100), { from: alice })
expect(await stakedPerpToken.balanceOf(alice)).to.eq(toFullDigit(100))
expect(await stakedPerpToken.totalSupply()).to.eq(toFullDigit(100))
expect(await stakedPerpToken.balanceOfAt(alice, blockNumber)).to.eq(toFullDigit(100))
expect(await stakedPerpToken.totalSupplyAt(blockNumber)).to.eq(toFullDigit(100))
await expectEvent.inTransaction(receipt.tx, stakedPerpToken, "Staked")
await expectEvent.inTransaction(receipt.tx, feeRewardPoolMock1, "NotificationReceived")
expect(await perpToken.balanceOf(alice)).to.eq(toFullDigit(1900))
})
it("alice stakes 100 and then stakes 400", async () => {
const prevBlockNumber = await stakedPerpToken.mock_getCurrentBlockNumber()
await stakedPerpToken.stake(toDecimal(100), { from: alice })
await forwardBlockTimestamp(15)
await stakedPerpToken.stake(toDecimal(400), { from: alice })
expect(await stakedPerpToken.balanceOfAt(alice, prevBlockNumber)).to.eq(toFullDigit(100))
const blockNumber = await stakedPerpToken.mock_getCurrentBlockNumber()
expect(await stakedPerpToken.balanceOf(alice)).to.eq(toFullDigit(500))
expect(await stakedPerpToken.totalSupply()).to.eq(toFullDigit(500))
expect(await stakedPerpToken.balanceOfAt(alice, blockNumber)).to.eq(toFullDigit(500))
expect(await stakedPerpToken.totalSupplyAt(blockNumber)).to.eq(toFullDigit(500))
expect(await perpToken.balanceOf(alice)).to.eq(toFullDigit(1500))
})
it("alice stakes 100 & admin stakes 300 in the same block", async () => {
const blockNumber = await stakedPerpToken.mock_getCurrentBlockNumber()
await stakedPerpToken.stake(toDecimal(100), { from: alice })
await stakedPerpToken.stake(toDecimal(300), { from: admin })
expect(await stakedPerpToken.balanceOf(alice)).to.eq(toFullDigit(100))
expect(await stakedPerpToken.balanceOfAt(alice, blockNumber)).to.eq(toFullDigit(100))
expect(await stakedPerpToken.balanceOf(admin)).to.eq(toFullDigit(300))
expect(await stakedPerpToken.balanceOfAt(admin, blockNumber)).to.eq(toFullDigit(300))
expect(await stakedPerpToken.totalSupply()).to.eq(toFullDigit(400))
expect(await stakedPerpToken.totalSupplyAt(blockNumber)).to.eq(toFullDigit(400))
})
it("alice stakes 100 and after one block admin stakes 300", async () => {
const blockNumberOfAlice = await stakedPerpToken.mock_getCurrentBlockNumber()
await stakedPerpToken.stake(toDecimal(100), { from: alice })
await forwardBlockTimestamp(15)
const blockNumberOfAdmin = await stakedPerpToken.mock_getCurrentBlockNumber()
await stakedPerpToken.stake(toDecimal(300), { from: admin })
expect(await stakedPerpToken.balanceOf(alice)).to.eq(toFullDigit(100))
expect(await stakedPerpToken.balanceOfAt(alice, blockNumberOfAlice)).to.eq(toFullDigit(100))
expect(await stakedPerpToken.balanceOfAt(alice, blockNumberOfAdmin)).to.eq(toFullDigit(100))
expect(await stakedPerpToken.balanceOf(admin)).to.eq(toFullDigit(300))
expect(await stakedPerpToken.balanceOfAt(admin, blockNumberOfAdmin)).to.eq(toFullDigit(300))
expect(await stakedPerpToken.totalSupply()).to.eq(toFullDigit(400))
expect(await stakedPerpToken.totalSupplyAt(blockNumberOfAdmin)).to.eq(toFullDigit(400))
})
it("force error, amount is zero", async () => {
await expectRevert(stakedPerpToken.stake(toDecimal(0), { from: alice }), "Amount is 0")
})
it("force error, balance is insufficient", async () => {
await expectRevert(
stakedPerpToken.stake(toDecimal(6000), { from: alice }),
"DecimalERC20: transferFrom failed",
)
})
it("force error, no stakeModule", async () => {
await stakedPerpToken.removeStakeModule(feeRewardPoolMock1.address)
await expectRevert(stakedPerpToken.stake(toDecimal(100), { from: alice }), "no stakeModule")
})
})
describe("unstake()", () => {
it("alice stakes 100 and then unstakes", async () => {
await stakedPerpToken.stake(toDecimal(100), { from: alice })
await forwardBlockTimestamp(15)
const blockNumber = await stakedPerpToken.mock_getCurrentBlockNumber()
const timestamp = await stakedPerpToken.mock_getCurrentTimestamp()
const receipt = await stakedPerpToken.unstake({ from: alice })
await expectEvent.inTransaction(receipt.tx, stakedPerpToken, "Unstaked")
await expectEvent.inTransaction(receipt.tx, feeRewardPoolMock1, "NotificationReceived")
expect(await stakedPerpToken.stakerCooldown(alice)).to.eq(timestamp.addn(cooldownPeriod))
expect(await stakedPerpToken.stakerWithdrawPendingBalance(alice)).to.eq(toFullDigit(100))
expect(await stakedPerpToken.balanceOf(alice)).to.eq(toFullDigit(0))
expect(await stakedPerpToken.totalSupply()).to.eq(toFullDigit(0))
expect(await stakedPerpToken.balanceOfAt(alice, blockNumber)).to.eq(toFullDigit(0))
expect(await stakedPerpToken.totalSupplyAt(blockNumber)).to.eq(toFullDigit(0))
expect(await perpToken.balanceOf(alice)).to.eq(toFullDigit(1900))
})
it("alice stakes 100, 200 and then unstakes", async () => {
await stakedPerpToken.stake(toDecimal(100), { from: alice })
await forwardBlockTimestamp(15)
await stakedPerpToken.stake(toDecimal(200), { from: alice })
await forwardBlockTimestamp(15)
await stakedPerpToken.unstake({ from: alice })
const blockNumber = await stakedPerpToken.mock_getCurrentBlockNumber()
expect(await stakedPerpToken.stakerWithdrawPendingBalance(alice)).to.eq(toFullDigit(300))
expect(await stakedPerpToken.balanceOf(alice)).to.eq(toFullDigit(0))
expect(await stakedPerpToken.totalSupply()).to.eq(toFullDigit(0))
expect(await stakedPerpToken.balanceOfAt(alice, blockNumber)).to.eq(toFullDigit(0))
expect(await stakedPerpToken.totalSupplyAt(blockNumber)).to.eq(toFullDigit(0))
expect(await perpToken.balanceOf(alice)).to.eq(toFullDigit(1700))
})
it("alice stakes 100, 200, unstakes and then admin stakes 300", async () => {
await stakedPerpToken.stake(toDecimal(100), { from: alice })
await forwardBlockTimestamp(15)
await stakedPerpToken.stake(toDecimal(200), { from: alice })
await forwardBlockTimestamp(15)
await stakedPerpToken.unstake({ from: alice })
await forwardBlockTimestamp(15)
await stakedPerpToken.stake(toDecimal(300), { from: admin })
const blockNumber = await stakedPerpToken.mock_getCurrentBlockNumber()
expect(await stakedPerpToken.stakerWithdrawPendingBalance(alice)).to.eq(toFullDigit(300))
expect(await stakedPerpToken.stakerWithdrawPendingBalance(admin)).to.eq(toFullDigit(0))
expect(await stakedPerpToken.balanceOf(alice)).to.eq(toFullDigit(0))
expect(await stakedPerpToken.balanceOf(admin)).to.eq(toFullDigit(300))
expect(await stakedPerpToken.totalSupply()).to.eq(toFullDigit(300))
expect(await stakedPerpToken.balanceOfAt(alice, blockNumber)).to.eq(toFullDigit(0))
expect(await stakedPerpToken.balanceOfAt(admin, blockNumber)).to.eq(toFullDigit(300))
expect(await stakedPerpToken.totalSupplyAt(blockNumber)).to.eq(toFullDigit(300))
expect(await perpToken.balanceOf(alice)).to.eq(toFullDigit(1700))
})
it("alice stakes 100, unstakes and then stakes 200 in the same block", async () => {
await stakedPerpToken.stake(toDecimal(100), { from: alice })
await forwardBlockTimestamp(15)
await stakedPerpToken.unstake({ from: alice })
const receipt = await stakedPerpToken.stake(toDecimal(200), { from: alice })
expect(await stakedPerpToken.stakerWithdrawPendingBalance(alice)).to.eq(toFullDigit(0))
expect(await stakedPerpToken.balanceOf(alice)).to.eq(toFullDigit(300))
expect(await perpToken.balanceOf(alice)).to.eq(toFullDigit(1700))
expectEvent(receipt, "Staked", {
amount: toFullDigit(300),
})
})
it("alice stakes 100, unstakes and then stakes 200 not in the same block/after cool down period", async () => {
await stakedPerpToken.stake(toDecimal(100), { from: alice })
await forwardBlockTimestamp(15)
await stakedPerpToken.unstake({ from: alice })
await forwardBlockTimestamp(cooldownPeriod * 2)
const receipt = await stakedPerpToken.stake(toDecimal(200), { from: alice })
expect(await stakedPerpToken.stakerWithdrawPendingBalance(alice)).to.eq(toFullDigit(0))
expect(await stakedPerpToken.balanceOf(alice)).to.eq(toFullDigit(300))
expect(await perpToken.balanceOf(alice)).to.eq(toFullDigit(1700))
expectEvent(receipt, "Staked", {
amount: toFullDigit(300),
})
})
it("force error, alice unstakes and then unstakes again", async () => {
await stakedPerpToken.stake(toDecimal(100), { from: alice })
await stakedPerpToken.unstake({ from: alice })
await forwardBlockTimestamp(15)
await expectRevert(stakedPerpToken.unstake({ from: alice }), "Need to withdraw first")
})
it("force error, alice unstakes without previous staking", async () => {
await expectRevert(stakedPerpToken.unstake({ from: alice }), "Amount is 0")
})
it("force error, no stakeModule", async () => {
await stakedPerpToken.removeStakeModule(feeRewardPoolMock1.address)
await expectRevert(stakedPerpToken.unstake({ from: alice }), "no stakeModule")
})
})
describe("withdraw()", () => {
it("alice stakes 100, unstakes and then withdraw", async () => {
await stakedPerpToken.stake(toDecimal(100), { from: alice })
await forwardBlockTimestamp(15)
await stakedPerpToken.unstake({ from: alice })
await forwardBlockTimestamp(15 * cooldownPeriod)
const receipt = await stakedPerpToken.withdraw({ from: alice })
await expectEvent.inTransaction(receipt.tx, stakedPerpToken, "Withdrawn")
expect(await stakedPerpToken.stakerWithdrawPendingBalance(alice)).to.eq(toFullDigit(0))
expect(await stakedPerpToken.stakerCooldown(alice)).to.eq(toFullDigit(0))
// alice should have 0 sPERP
expect(await stakedPerpToken.balanceOf(alice)).to.eq(toFullDigit(0))
expect(await perpToken.balanceOf(alice)).to.eq(toFullDigit(2000))
})
it("force error, alice stakes 100, unstakes and then withdraw within cooling down period", async () => {
await stakedPerpToken.stake(toDecimal(100), { from: alice })
await forwardBlockTimestamp(15)
await stakedPerpToken.unstake({ from: alice })
await forwardBlockTimestamp(cooldownPeriod - 1)
await expectRevert(stakedPerpToken.withdraw({ from: alice }), "Still in cooldown")
})
it("force error, alice withdraw without previous staking", async () => {
await expectRevert(stakedPerpToken.withdraw({ from: alice }), "Amount is 0")
})
it("force error, alice withdraw without previous unstaking", async () => {
await stakedPerpToken.stake(toDecimal(100), { from: alice })
await forwardBlockTimestamp(15 * cooldownPeriod)
await expectRevert(stakedPerpToken.withdraw({ from: alice }), "Amount is 0")
})
it("force error, alice stakes 100, unstakes and then withdraw twice", async () => {
await stakedPerpToken.stake(toDecimal(100), { from: alice })
await stakedPerpToken.unstake({ from: alice })
await forwardBlockTimestamp(cooldownPeriod)
await stakedPerpToken.withdraw({ from: alice })
await expectRevert(stakedPerpToken.withdraw({ from: alice }), "Amount is 0")
})
})
describe("addStakeModule()", () => {
it("stakeModules should be set", async () => {
await stakedPerpToken.addStakeModule(feeRewardPoolMock2.address)
expect(await stakedPerpToken.stakeModules(0)).to.eq(feeRewardPoolMock1.address)
expect(await stakedPerpToken.stakeModules(1)).to.eq(feeRewardPoolMock2.address)
expect(await stakedPerpToken.isStakeModuleExisted(feeRewardPoolMock1.address)).to.eq(true)
expect(await stakedPerpToken.isStakeModuleExisted(feeRewardPoolMock2.address)).to.eq(true)
})
it("force error, onlyOwner", async () => {
await expectRevert(
stakedPerpToken.addStakeModule(feeRewardPoolMock1.address, { from: alice }),
"PerpFiOwnableUpgrade: caller is not the owner",
)
})
it("force error, stakeModule is already existed", async () => {
await expectRevert(stakedPerpToken.addStakeModule(feeRewardPoolMock1.address), "invalid input")
})
it("force error, input is zero address", async () => {
await expectRevert(stakedPerpToken.addStakeModule(EMPTY_ADDRESS), "invalid input")
})
})
describe("removeStakeModule()", () => {
it("stakeModule should be removed", async () => {
await stakedPerpToken.removeStakeModule(feeRewardPoolMock1.address)
expect(await stakedPerpToken.isStakeModuleExisted(feeRewardPoolMock1.address)).to.eq(false)
expect(await stakedPerpToken.getStakeModuleLength()).to.eq(0)
})
it("stakeModules should be removed and can be added again", async () => {
await stakedPerpToken.addStakeModule(feeRewardPoolMock2.address)
await stakedPerpToken.removeStakeModule(feeRewardPoolMock1.address)
await stakedPerpToken.addStakeModule(feeRewardPoolMock1.address)
expect(await stakedPerpToken.stakeModules(0)).to.eq(feeRewardPoolMock2.address)
expect(await stakedPerpToken.stakeModules(1)).to.eq(feeRewardPoolMock1.address)
})
it("force error, onlyOwner", async () => {
await expectRevert(
stakedPerpToken.removeStakeModule(feeRewardPoolMock1.address, { from: alice }),
"PerpFiOwnableUpgrade: caller is not the owner",
)
})
it("force error, stakeModule does not exist", async () => {
await expectRevert(
stakedPerpToken.removeStakeModule(feeRewardPoolMock2.address),
"stakeModule does not exist",
)
})
it("force error, input is zero address", async () => {
await expectRevert(stakedPerpToken.removeStakeModule(EMPTY_ADDRESS), "stakeModule does not exist")
})
it("force error, no stakeModule", async () => {
await stakedPerpToken.removeStakeModule(feeRewardPoolMock1.address)
await expectRevert(
stakedPerpToken.removeStakeModule(feeRewardPoolMock1.address),
"stakeModule does not exist",
)
})
})
describe("setCooldownPeriod()", () => {
it("set to 5 days", async () => {
await stakedPerpToken.setCooldownPeriod(new BN(60 * 60 * 24 * 5))
const cooldownPeriod = await stakedPerpToken.cooldownPeriod()
expect(cooldownPeriod).to.eq(432000)
})
it("force error, cooldownPeriod cannot be 0", async () => {
await expectRevert(stakedPerpToken.setCooldownPeriod(new BN(0)), "invalid input")
})
})
})
Example #21
Source File: SupplyScheduleSpec.ts From perpetual-protocol with GNU General Public License v3.0 | 4 votes |
// skip, won't be in v1
describe.skip("Supply Schedule Unit Test", () => {
let admin: string
let alice: string
let perpToken: PerpTokenMockInstance
let supplySchedule: SupplyScheduleFakeInstance
let minter: MinterInstance
const inflationRate = toFullDigit(0.01)
const decayRate = toFullDigit(0.01)
const mintDuration = new BN(7 * 24 * 60 * 60) // 7 days
const supplyDecayPeriod = new BN(7 * 24 * 60 * 60 * 209) // 209 weeks
beforeEach(async () => {
const addresses = await web3.eth.getAccounts()
admin = addresses[0]
alice = addresses[1]
perpToken = await deployPerpTokenMock()
minter = await deployMinter(perpToken.address)
supplySchedule = await deploySupplySchedule(minter.address, inflationRate, decayRate, mintDuration)
})
async function gotoNextMintTime(): Promise<void> {
const nextMintTime = await supplySchedule.nextMintTime()
await supplySchedule.mock_setBlockTimestamp(nextMintTime)
}
describe("isMintable", () => {
it("is not mintable before start", async () => {
expect(await supplySchedule.isMintable()).be.false
})
it("is not mintable before start", async () => {
await supplySchedule.startSchedule()
expect(await supplySchedule.isMintable()).be.false
await gotoNextMintTime()
expect(await supplySchedule.isMintable()).be.true
})
})
describe("startSchedule", async () => {
it("can't start by account which is not owner", async () => {
await expectRevert(
supplySchedule.startSchedule({ from: alice }),
"PerpFiOwnableUpgrade: caller is not the owner",
)
})
it("start after a while", async () => {
expect(await supplySchedule.supplyDecayEndTime()).eq(0)
await supplySchedule.startSchedule()
const now = await supplySchedule.mock_getCurrentTimestamp()
const supplyDecayEndTime = now.add(supplyDecayPeriod)
expect(await supplySchedule.supplyDecayEndTime()).eq(supplyDecayEndTime)
})
})
describe("mintableSupply", () => {
it("zero when it's not mintable", async () => {
expect(await supplySchedule.mintableSupply()).eq(0)
})
it("based on inflationRate before decay end", async () => {
await supplySchedule.startSchedule()
await gotoNextMintTime()
await perpToken.setTotalSupply(toFullDigit(100))
// 100 * 1% = 1
expect(await supplySchedule.mintableSupply()).eq(toFullDigit(1))
})
it("will keeps the fixed inflationRate after decay end", async () => {
await supplySchedule.startSchedule()
const now = await supplySchedule.mock_getCurrentTimestamp()
const supplyDecayEndTime = now.add(supplyDecayPeriod)
await supplySchedule.mock_setBlockTimestamp(supplyDecayEndTime)
await perpToken.setTotalSupply(toFullDigit(100))
// 100 * 0.04749% ~= 0.04749
expect(await supplySchedule.mintableSupply()).eq("47497069730730000")
})
})
})
Example #22
Source File: TollPoolSpec.ts From perpetual-protocol with GNU General Public License v3.0 | 4 votes |
describe("tollPoolSpec", () => {
let admin: string
let alice: string
let feeTokenPoolDispatcherMock: string
let tokenMediator: MultiTokenMediatorMockInstance
let tollPool: TollPoolInstance
let usdt: ERC20FakeInstance
let usdc: ERC20FakeInstance
beforeEach(async () => {
const addresses = await web3.eth.getAccounts()
admin = addresses[0]
alice = addresses[1]
feeTokenPoolDispatcherMock = addresses[2]
usdt = await deployErc20Fake(toFullDigit(2000000))
usdc = await deployErc20Fake(toFullDigit(2000000))
tokenMediator = await deployMockMultiToken()
const ambBridge = await deployMockAMBBridge()
const clientBridge = await deployClientBridge(ambBridge.address, tokenMediator.address, admin)
tollPool = await deployTollPool(admin, clientBridge.address)
await usdt.approve(tollPool.address, toFullDigit(2000000))
await usdc.approve(tollPool.address, toFullDigit(2000000))
})
describe("transferToFeeTokenPoolDispatcher()", () => {
it("can't add empty token", async () => {
await expectRevert(tollPool.addFeeToken(EMPTY_ADDRESS), "invalid input")
})
it("should receive all the balance of one token in the tollPool contract", async () => {
await tollPool.setFeeTokenPoolDispatcher(feeTokenPoolDispatcherMock)
await tollPool.addFeeToken(usdt.address)
await tollPool.addFeeToken(usdc.address)
await usdt.transfer(tollPool.address, toFullDigit(1000))
const receipt = await tollPool.transferToFeeTokenPoolDispatcher({ from: admin })
expectEvent.inTransaction(receipt.tx, tollPool, "TokenTransferred")
expect(await usdt.balanceOf(tokenMediator.address)).to.eq(toFullDigit(1000))
})
it("should receive all the balances of tokens in the tollPool contract", async () => {
await tollPool.setFeeTokenPoolDispatcher(feeTokenPoolDispatcherMock)
await tollPool.addFeeToken(usdt.address)
await tollPool.addFeeToken(usdc.address)
await usdt.transfer(tollPool.address, toFullDigit(1000))
await usdc.transfer(tollPool.address, toFullDigit(2000))
const receipt = await tollPool.transferToFeeTokenPoolDispatcher({ from: admin })
expectEvent.inTransaction(receipt.tx, tollPool, "TokenTransferred", {
token: usdt.address,
amount: toFullDigit(1000),
})
// expectEvent.inTransaction(receipt.tx, tollPool, "TokenTransferred", {
// token: usdc.address,
// amount: toFullDigit(2000),
// })
expect(await usdt.balanceOf(tokenMediator.address)).to.eq(toFullDigit(1000))
expect(await usdc.balanceOf(tokenMediator.address)).to.eq(toFullDigit(2000))
})
it("should receive usdt but not usdc, since the balance of usdc is 0", async () => {
await tollPool.setFeeTokenPoolDispatcher(feeTokenPoolDispatcherMock)
await tollPool.addFeeToken(usdt.address)
await tollPool.addFeeToken(usdc.address)
await usdt.transfer(tollPool.address, toFullDigit(1000))
await tollPool.transferToFeeTokenPoolDispatcher({ from: admin })
expect(await usdt.balanceOf(tokenMediator.address)).to.eq(toFullDigit(1000))
})
it("force error, feeTokenPoolDispatcherL1 not yet set", async () => {
await expectRevert(tollPool.transferToFeeTokenPoolDispatcher(), "feeTokenPoolDispatcherL1 not yet set")
})
it("force error, feeTokens not yet set", async () => {
await tollPool.setFeeTokenPoolDispatcher(feeTokenPoolDispatcherMock)
await expectRevert(tollPool.transferToFeeTokenPoolDispatcher(), "feeTokens not set yet")
})
it("force error, the amount of all registered token is zero", async () => {
await tollPool.setFeeTokenPoolDispatcher(feeTokenPoolDispatcherMock)
await tollPool.addFeeToken(usdt.address)
await expectRevert(tollPool.transferToFeeTokenPoolDispatcher(), "fee is now zero")
})
})
describe("setFeeTokenPoolDispatcher()", () => {
it("feeTokenPoolDispatcher should be set", async () => {
await tollPool.setFeeTokenPoolDispatcher(feeTokenPoolDispatcherMock)
expect(await tollPool.feeTokenPoolDispatcherL1()).to.eq(feeTokenPoolDispatcherMock)
})
it("feeTokenPoolDispatcher should be updated", async () => {
await tollPool.setFeeTokenPoolDispatcher(feeTokenPoolDispatcherMock)
await tollPool.setFeeTokenPoolDispatcher(alice)
expect(await tollPool.feeTokenPoolDispatcherL1()).to.eq(alice)
})
it("force error, onlyOwner", async () => {
await expectRevert(
tollPool.setFeeTokenPoolDispatcher(EMPTY_ADDRESS, { from: alice }),
"PerpFiOwnableUpgrade: caller is not the owner",
)
})
it("force error, input is zero address", async () => {
await expectRevert(tollPool.setFeeTokenPoolDispatcher(EMPTY_ADDRESS), "invalid input")
})
it("force error, feeTokenPoolDispatcher already existed", async () => {
await tollPool.setFeeTokenPoolDispatcher(feeTokenPoolDispatcherMock)
await expectRevert(
tollPool.setFeeTokenPoolDispatcher(feeTokenPoolDispatcherMock),
"input is the same as the current one",
)
})
})
describe("addFeeToken()", () => {
it("feeTokens should be set", async () => {
await tollPool.addFeeToken(usdt.address)
await tollPool.addFeeToken(usdc.address)
expect(await tollPool.feeTokens(0)).to.eq(usdt.address)
expect(await tollPool.feeTokens(1)).to.eq(usdc.address)
expect(await tollPool.isFeeTokenExisted(usdt.address)).to.eq(true)
expect(await tollPool.isFeeTokenExisted(usdc.address)).to.eq(true)
})
it("force error, onlyOwner", async () => {
await expectRevert(
tollPool.addFeeToken(usdt.address, { from: alice }),
"PerpFiOwnableUpgrade: caller is not the owner",
)
})
it("force error, token is already existed", async () => {
await tollPool.addFeeToken(usdt.address)
await expectRevert(tollPool.addFeeToken(usdt.address), "invalid input")
})
it("force error, input is zero address", async () => {
await expectRevert(tollPool.addFeeToken(EMPTY_ADDRESS), "invalid input")
})
})
describe("removeFeeToken()", () => {
it("feeTokens should be removed", async () => {
await tollPool.addFeeToken(usdt.address)
await tollPool.removeFeeToken(usdt.address)
expect(await tollPool.isFeeTokenExisted(usdt.address)).to.eq(false)
expect(await tollPool.getFeeTokenLength()).to.eq(0)
})
it("feeTokens should be removed and can be added again", async () => {
await tollPool.addFeeToken(usdt.address)
await tollPool.addFeeToken(usdc.address)
await tollPool.removeFeeToken(usdt.address)
await tollPool.addFeeToken(usdt.address)
expect(await tollPool.feeTokens(0)).to.eq(usdc.address)
expect(await tollPool.feeTokens(1)).to.eq(usdt.address)
})
it("should transfer to feeTokenPoolDispatcher via clientBridge before removeFeeToken", async () => {
await tollPool.setFeeTokenPoolDispatcher(feeTokenPoolDispatcherMock)
await tollPool.addFeeToken(usdt.address)
await usdt.transfer(tollPool.address, 1)
// TODO expect tollPool call clientBridge.erc20Transfer(feeTokenPoolDispatcher)
// let's use ethers/waffle when writing new unit test. it's hard to write unit test without mock lib
await tollPool.removeFeeToken(usdt.address)
expect(await usdt.balanceOf(tollPool.address)).eq(0)
})
it("force error, onlyOwner", async () => {
await tollPool.addFeeToken(usdt.address)
await expectRevert(
tollPool.removeFeeToken(usdt.address, { from: alice }),
"PerpFiOwnableUpgrade: caller is not the owner",
)
})
it("force error, token does not exist", async () => {
await expectRevert(tollPool.removeFeeToken(usdt.address), "token does not exist")
})
it("force error, input is zero address", async () => {
await tollPool.addFeeToken(usdt.address)
await expectRevert(tollPool.removeFeeToken(EMPTY_ADDRESS), "token does not exist")
})
})
})
Example #23
Source File: ExchangeWrapper.ts From perpetual-protocol with GNU General Public License v3.0 | 4 votes |
// kovan
// Perp Token: 0xbe216554aF577Ae8F6CE7415D2d08A224b3Cdc1b
// Compound:
// cUSDT : 0x3f0a0ea2f86bae6362cf9799b523ba06647da018
// USDT: 0x07de306ff27a2b630b1141956844eb1552b956b5
// Balancer:
// Perp/cUSDT : 0x081D79E1c970DF4efF76aF11352121B06C779945
describe.skip("ExchangeWrapper testing code on Kovan", () => {
let addresses: string[]
let admin: string
let exchangeWrapper: ExchangeWrapperInstance
let perpToken: PerpTokenInstance
let cToken: IERC20Instance
let cUSDT: string
let balancerPool: string
let usdt: string
let erc20: IERC20Instance
before(async () => {
addresses = await web3.eth.getAccounts()
admin = addresses[0]
// perpToken = await deployPerpToken(toFullDigit(1000000))
perpToken = await PerpToken.at("0xbe216554aF577Ae8F6CE7415D2d08A224b3Cdc1b")
// Compound
usdt = "0x07de306ff27a2b630b1141956844eb1552b956b5"
cUSDT = "0x3f0a0ea2f86bae6362cf9799b523ba06647da018"
cToken = await ERC20.at(cUSDT)
erc20 = await ERC20.at(usdt)
// balancer
balancerPool = "0x081D79E1c970DF4efF76aF11352121B06C779945" // Perp/cUSDT
// exchangeWrapper = await ExchangeWrapper.at("0xc83B115a58b91fc8ab319F4953CbBEBA3b55836D")
exchangeWrapper = await deployExchangeWrapper(balancerPool, cUSDT, perpToken.address)
console.log(exchangeWrapper.address)
await perpToken.approve(exchangeWrapper.address, toFullDigit(5000000))
await erc20.approve(exchangeWrapper.address, toFullDigit(1000000))
})
async function balanceOf(): Promise<void> {
const perp = await perpToken.balanceOf(exchangeWrapper.address)
const ctoken = await cToken.balanceOf(exchangeWrapper.address)
const token = await erc20.balanceOf(exchangeWrapper.address)
console.log("perp ", perp.toString(), "cusdt ", ctoken.toString(), "usdt ", token.toString())
}
beforeEach(async () => {
await balanceOf()
})
afterEach(async () => {
await balanceOf()
})
it("getSpotPrice, usdt in", async () => {
const r = await exchangeWrapper.getSpotPrice(usdt, perpToken.address)
console.log(r.d.toString())
// 0.02847868160384
})
it("getSpotPrice, usdt out", async () => {
const r = await exchangeWrapper.getSpotPrice(perpToken.address, usdt)
console.log(r.d.toString())
// 35.117516937919847382
})
it("swapInput, usdt out", async () => {
// gas consumption: 410,499
const r = await exchangeWrapper.swapInput(perpToken.address, usdt, toDecimal(50), toDecimal(0), toDecimal(0))
console.log("tx", r.tx)
console.log(r.logs[0].event)
console.log(r.logs[0].args[0].toString())
console.log(r.logs[0].args[1].toString())
console.log(r.logs[1].event)
console.log(r.logs[1].args[0].toString())
console.log(r.logs[1].args[1].toString())
console.log(r.logs[2].event)
console.log(r.logs[2].args[0].toString())
console.log(r.logs[2].args[1].toString())
// BalancerSwap
// 50000000000000000000
// 70985940
// CompoundRedeem
// 70985940
// 1495797
})
it("swapInput, usdt in", async () => {
// gas consumption: 410,499
const r = await exchangeWrapper.swapInput(usdt, perpToken.address, toDecimal(0.5), toDecimal(0), toDecimal(0))
console.log("tx", r.tx)
console.log(r.logs[0].event)
console.log(r.logs[0].args[0].toString())
console.log(r.logs[0].args[1].toString())
console.log(r.logs[1].event)
console.log(r.logs[1].args[0].toString())
console.log(r.logs[1].args[1].toString())
console.log(r.logs[2].event)
console.log(r.logs[2].args[0].toString())
console.log(r.logs[2].args[1].toString())
})
it("swapOutput, usdt out", async () => {
// gas consumption: 431,645
const r = await exchangeWrapper.swapOutput(
perpToken.address,
usdt,
toDecimal(0.5),
toDecimal(10000),
toDecimal(0),
)
console.log("tx", r.tx)
console.log(r.logs[0].event)
console.log(r.logs[0].args[0].toString())
console.log(r.logs[0].args[1].toString())
console.log(r.logs[1].event)
console.log(r.logs[1].args[0].toString())
console.log(r.logs[1].args[1].toString())
console.log(r.logs[2].event)
console.log(r.logs[2].args[0].toString())
console.log(r.logs[2].args[1].toString())
// BalancerSwap
// 16829970323559010254
// 23728463
// CompoundRedeem
// 23728463
// 500000
})
it("swapOutput, usdt in", async () => {
// gas consumption: 431,645
const r = await exchangeWrapper.swapOutput(
usdt,
perpToken.address,
toDecimal(50),
toDecimal(1000000),
toDecimal(0),
)
console.log("tx", r.tx)
console.log(r.logs[0].event)
console.log(r.logs[0].args[0].toString())
console.log(r.logs[0].args[1].toString())
console.log(r.logs[1].event)
console.log(r.logs[1].args[0].toString())
console.log(r.logs[1].args[1].toString())
console.log(r.logs[2].event)
console.log(r.logs[2].args[0].toString())
console.log(r.logs[2].args[1].toString())
})
})
Example #24
Source File: ExchangeWrapper.ts From perpetual-protocol with GNU General Public License v3.0 | 4 votes |
// skip, won't be in v1
describe.skip("ExchangeWrapper UT", () => {
let addresses: string[]
let admin: string
let alice: string
let exchangeWrapper: ExchangeWrapperInstance
let tether: TetherTokenInstance
let usdc: TetherTokenInstance
let erc20Fake: ERC20FakeInstance
let CUsdt: CUsdtMockInstance
let balancer: BalancerMockInstance
before(async () => {
addresses = await web3.eth.getAccounts()
admin = addresses[0]
alice = addresses[1]
tether = await TetherToken.new(toFullDigit(1000), "Tether", "USDT", 6)
usdc = await TetherToken.new(toFullDigit(1000), "USDC", "USDC", 6)
erc20Fake = await deployErc20Fake(toFullDigit(10000), "NAME", "SYMBOL")
CUsdt = await deployMockCUsdt()
balancer = await deployMockBalancer(erc20Fake.address, CUsdt.address)
await CUsdt.mockSetUnderlying(tether.address)
exchangeWrapper = await deployExchangeWrapper(balancer.address, CUsdt.address, erc20Fake.address)
})
it("getSpotPrice, usdc in", async () => {
// tether 6 decimals, erc20Fake 18 decimals
// spot price will be n*(e-(18-6))*e18 = n*(e-12)*e18 = n*e6
// assuming n = 1 here
await balancer.mockSetSpotPrice("1000000")
const r = await exchangeWrapper.getSpotPrice(usdc.address, erc20Fake.address)
expect(r).to.eq(toFullDigit(1))
})
it("getSpotPrice, usdc out", async () => {
// tether 6 decimals, erc20Fake 18 decimals
// spot price will be n*(e(18-6))*e18 = n*(e12)*e18 = n*e30
// assuming n = 1 here
await balancer.mockSetSpotPrice(toFullDigit(1000000000000))
const r = await exchangeWrapper.getSpotPrice(erc20Fake.address, usdc.address)
expect(r).to.eq(toFullDigit(1))
})
it("getSpotPrice, tether in", async () => {
// cToken 8 decimals, erc20Fake 18 decimals
// spot price will be n*(e-(18-8))*e18 = n*(e-10)*e18 = n*e8
// assuming n = 1 here
await balancer.mockSetSpotPrice("100000000")
// set exchange ratio of cToken to 0.01, which means cUSDT:USDT = 1:1
// 0.01 represents the decimal difference 8 decimals of cUSDT and 6 decimals of USDT
await CUsdt.mockSetExchangeRateStored(toFullDigit(0.01))
const r = await exchangeWrapper.getSpotPrice(tether.address, erc20Fake.address)
expect(r).to.eq(toFullDigit(1))
})
it("getSpotPrice, tether out", async () => {
// cToken 8 decimals, erc20Fake 18 decimals
// spot price will be n*(e(18-8))*e18 = n*(e10)*e18 = n*e28
// assuming n = 1 here
await balancer.mockSetSpotPrice(toFullDigit(10000000000))
// set exchange ratio of cToken to 0.01, which means cUSDT:USDT = 1:1
// 0.01 represents the decimal difference 8 decimals of cUSDT and 6 decimals of USDT
await CUsdt.mockSetExchangeRateStored(toFullDigit(0.01))
await CUsdt.mockSetExchangeRateStored(toFullDigit(0.01))
const r = await exchangeWrapper.getSpotPrice(erc20Fake.address, tether.address)
expect(r).to.eq(toFullDigit(1))
})
it("getSpotPrice, erc20 in/out", async () => {
const spotPrice = await exchangeWrapper.getSpotPrice(erc20Fake.address, erc20Fake.address)
expect(spotPrice).to.eq(toFullDigit(1))
})
it("getSpotPrice, usdc in/out", async () => {
const spotPrice = await exchangeWrapper.getSpotPrice(usdc.address, usdc.address)
expect(spotPrice).to.eq(toFullDigit(1))
})
it("getSpotPrice, usdt in/out", async () => {
const spotPrice = await exchangeWrapper.getSpotPrice(tether.address, tether.address)
expect(spotPrice).to.eq(toFullDigit(1))
})
it("force error, only owner can setBalancerPool", async () => {
await expectRevert(
exchangeWrapper.setBalancerPool(alice, { from: alice }),
"PerpFiOwnableUpgrade: caller is not the owner",
)
})
it("force error, only owner can setCompoundCUsdt", async () => {
await expectRevert(
exchangeWrapper.setCompoundCUsdt(alice, { from: alice }),
"PerpFiOwnableUpgrade: caller is not the owner",
)
})
it("force error, only owner can approve", async () => {
await expectRevert(
exchangeWrapper.approve(alice, alice, toDecimal(10), { from: alice }),
"PerpFiOwnableUpgrade: caller is not the owner",
)
})
})
Example #25
Source File: ChainlinkPriceFeedSpec.ts From perpetual-protocol with GNU General Public License v3.0 | 4 votes |
describe("ChainlinkPriceFeed Spec", () => {
const CHAINLINK_DECIMAL = 8
let addresses: string[]
let priceFeed!: ChainlinkPriceFeedFakeInstance
let chainlinkMock1!: ChainlinkL1MockInstance
let chainlinkMock2!: ChainlinkL1MockInstance
let chainlinkMock3!: ChainlinkL1MockInstance
const EMPTY_ADDRESS = "0x0000000000000000000000000000000000000000"
beforeEach(async () => {
addresses = await web3.eth.getAccounts()
chainlinkMock1 = await deployChainlinkL1Mock()
chainlinkMock2 = await deployChainlinkL1Mock()
chainlinkMock3 = await deployChainlinkL1Mock()
priceFeed = await deployChainlinkPriceFeed()
})
function stringToBytes32(str: string): string {
return web3.utils.asciiToHex(str)
}
function fromBytes32(str: string): string {
return web3.utils.hexToUtf8(str)
}
describe("addAggregator", () => {
it("getAggregator with existed aggregator key", async () => {
await priceFeed.addAggregator(stringToBytes32("ETH"), chainlinkMock1.address)
expect(fromBytes32(await priceFeed.priceFeedKeys(0))).eq("ETH")
expect(await priceFeed.getAggregator(stringToBytes32("ETH"))).eq(chainlinkMock1.address)
expect(await priceFeed.priceFeedDecimalMap(stringToBytes32("ETH"))).eq(8)
})
it("getAggregator with non-existed aggregator key", async () => {
await priceFeed.addAggregator(stringToBytes32("ETH"), chainlinkMock1.address)
expect(await priceFeed.getAggregator(stringToBytes32("BTC"))).eq(EMPTY_ADDRESS)
})
it("add multi aggregators", async () => {
await priceFeed.addAggregator(stringToBytes32("ETH"), chainlinkMock1.address)
await priceFeed.addAggregator(stringToBytes32("BTC"), chainlinkMock2.address)
await priceFeed.addAggregator(stringToBytes32("LINK"), chainlinkMock3.address)
expect(fromBytes32(await priceFeed.priceFeedKeys(0))).eq("ETH")
expect(await priceFeed.getAggregator(stringToBytes32("ETH"))).eq(chainlinkMock1.address)
expect(fromBytes32(await priceFeed.priceFeedKeys(2))).eq("LINK")
expect(await priceFeed.getAggregator(stringToBytes32("LINK"))).eq(chainlinkMock3.address)
})
it("force error, addAggregator with zero address", async () => {
await expectRevert(priceFeed.addAggregator(stringToBytes32("ETH"), EMPTY_ADDRESS), "empty address")
})
})
describe("removeAggregator", () => {
it("remove 1 aggregator when there's only 1", async () => {
await priceFeed.addAggregator(stringToBytes32("ETH"), chainlinkMock1.address)
await priceFeed.removeAggregator(stringToBytes32("ETH"))
// cant use expectRevert because the error message is different between CI and local env
let error
try {
await priceFeed.priceFeedKeys(0)
} catch (e) {
error = e
}
expect(error).not.eq(undefined)
expect(await priceFeed.getAggregator(stringToBytes32("ETH"))).eq(EMPTY_ADDRESS)
})
it("remove 1 aggregator when there're 2", async () => {
await priceFeed.addAggregator(stringToBytes32("ETH"), chainlinkMock1.address)
await priceFeed.addAggregator(stringToBytes32("BTC"), chainlinkMock1.address)
await priceFeed.removeAggregator(stringToBytes32("ETH"))
expect(fromBytes32(await priceFeed.priceFeedKeys(0))).eq("BTC")
expect(await priceFeed.getAggregator(stringToBytes32("ETH"))).eq(EMPTY_ADDRESS)
expect(await priceFeed.getAggregator(stringToBytes32("BTC"))).eq(chainlinkMock1.address)
})
})
describe("twap", () => {
beforeEach(async () => {
await priceFeed.addAggregator(stringToBytes32("ETH"), chainlinkMock1.address)
const currentTime = await priceFeed.mock_getCurrentTimestamp()
await chainlinkMock1.mockAddAnswer(0, toFullDigit(400, CHAINLINK_DECIMAL), currentTime, currentTime, 0)
const firstTimestamp = currentTime.addn(15)
await chainlinkMock1.mockAddAnswer(
1,
toFullDigit(405, CHAINLINK_DECIMAL),
firstTimestamp,
firstTimestamp,
1,
)
const secondTimestamp = firstTimestamp.addn(15)
await chainlinkMock1.mockAddAnswer(
2,
toFullDigit(410, CHAINLINK_DECIMAL),
secondTimestamp,
secondTimestamp,
2,
)
const thirdTimestamp = secondTimestamp.addn(15)
await priceFeed.mock_setBlockTimestamp(thirdTimestamp)
})
// aggregator's answer
// timestamp(base + 0) : 400
// timestamp(base + 15) : 405
// timestamp(base + 30) : 410
// now = base + 45
//
// --+------+-----+-----+-----+-----+-----+
// base now
it("twap price", async () => {
const price = await priceFeed.getTwapPrice(stringToBytes32("ETH"), 45)
expect(price).to.eq(toFullDigit(405))
})
it("asking interval more than aggregator has", async () => {
const price = await priceFeed.getTwapPrice(stringToBytes32("ETH"), 46)
expect(price).to.eq(toFullDigit(405))
})
it("asking interval less than aggregator has", async () => {
const price = await priceFeed.getTwapPrice(stringToBytes32("ETH"), 44)
expect(price).to.eq("405113636360000000000")
})
it("given variant price period", async () => {
const currentTime = await priceFeed.mock_getCurrentTimestamp()
await chainlinkMock1.mockAddAnswer(
4,
toFullDigit(420, CHAINLINK_DECIMAL),
currentTime.addn(30),
currentTime.addn(30),
4,
)
await priceFeed.mock_setBlockTimestamp(currentTime.addn(50))
// twap price should be (400 * 15) + (405 * 15) + (410 * 45) + (420 * 20) / 95 = 409.74
const price = await priceFeed.getTwapPrice(stringToBytes32("ETH"), 95)
expect(price).to.eq("409736842100000000000")
})
it("latest price update time is earlier than the request, return the latest price", async () => {
const currentTime = await priceFeed.mock_getCurrentTimestamp()
await priceFeed.mock_setBlockTimestamp(currentTime.addn(100))
// latest update time is base + 30, but now is base + 145 and asking for (now - 45)
// should return the latest price directly
const price = await priceFeed.getTwapPrice(stringToBytes32("ETH"), 45)
expect(price).to.eq(toFullDigit(410))
})
it("if current price < 0, ignore the current price", async () => {
await chainlinkMock1.mockAddAnswer(3, toFullDigit(-10, CHAINLINK_DECIMAL), 250, 250, 3)
const price = await priceFeed.getTwapPrice(stringToBytes32("ETH"), 45)
expect(price).to.eq(toFullDigit(405))
})
it("if there is a negative price in the middle, ignore that price", async () => {
const currentTime = await priceFeed.mock_getCurrentTimestamp()
await chainlinkMock1.mockAddAnswer(
3,
toFullDigit(-100, CHAINLINK_DECIMAL),
currentTime.addn(20),
currentTime.addn(20),
3,
)
await chainlinkMock1.mockAddAnswer(
4,
toFullDigit(420, CHAINLINK_DECIMAL),
currentTime.addn(30),
currentTime.addn(30),
4,
)
await priceFeed.mock_setBlockTimestamp(currentTime.addn(50))
// twap price should be (400 * 15) + (405 * 15) + (410 * 45) + (420 * 20) / 95 = 409.74
const price = await priceFeed.getTwapPrice(stringToBytes32("ETH"), 95)
expect(price).to.eq("409736842100000000000")
})
it("force error, interval is zero", async () => {
await expectRevert(priceFeed.getTwapPrice(stringToBytes32("ETH"), 0), "interval can't be 0")
})
})
describe("getprice/getLatestTimestamp/getPreviousPrice/getPreviousTimestamp", () => {
beforeEach(async () => {
await priceFeed.addAggregator(stringToBytes32("ETH"), chainlinkMock1.address)
await chainlinkMock1.mockAddAnswer(0, toFullDigit(400, CHAINLINK_DECIMAL), 100, 100, 0)
await chainlinkMock1.mockAddAnswer(1, toFullDigit(405, CHAINLINK_DECIMAL), 150, 150, 1)
await chainlinkMock1.mockAddAnswer(2, toFullDigit(410, CHAINLINK_DECIMAL), 200, 200, 2)
})
it("getPrice/getTimestamp", async () => {
const price = await priceFeed.getPrice(stringToBytes32("ETH"))
expect(price).to.eq(toFullDigit(410))
const timestamp = await priceFeed.getLatestTimestamp(stringToBytes32("ETH"))
expect(timestamp).to.eq(200)
})
it("latest getPreviousPrice/getPreviousTimestamp", async () => {
const price = await priceFeed.getPreviousPrice(stringToBytes32("ETH"), 0)
expect(price).to.eq(toFullDigit(410))
const timestamp = await priceFeed.getPreviousTimestamp(stringToBytes32("ETH"), 0)
expect(timestamp).to.eq(200)
})
it("non-latest getPreviousPrice/getPreviousTimestamp", async () => {
const price = await priceFeed.getPreviousPrice(stringToBytes32("ETH"), 2)
expect(price).to.eq(toFullDigit(400))
const timestamp = await priceFeed.getPreviousTimestamp(stringToBytes32("ETH"), 2)
expect(timestamp).to.eq(100)
})
it("if current price < 0, return the latest positive price and the according timestamp", async () => {
await chainlinkMock1.mockAddAnswer(3, toFullDigit(-10, CHAINLINK_DECIMAL), 250, 250, 3)
let price = await priceFeed.getPrice(stringToBytes32("ETH"))
expect(price).to.eq(toFullDigit(410))
let timestamp = await priceFeed.getLatestTimestamp(stringToBytes32("ETH"))
expect(timestamp).to.eq(200)
await chainlinkMock1.mockAddAnswer(4, toFullDigit(-120, CHAINLINK_DECIMAL), 300, 300, 4)
price = await priceFeed.getPrice(stringToBytes32("ETH"))
expect(price).to.eq(toFullDigit(410))
timestamp = await priceFeed.getLatestTimestamp(stringToBytes32("ETH"))
expect(timestamp).to.eq(200)
})
it("force error, getPreviousPrice/getPreviousTimestamp fail if the price at that time < 0", async () => {
await chainlinkMock1.mockAddAnswer(3, toFullDigit(-10, CHAINLINK_DECIMAL), 250, 250, 3)
await expectRevert(priceFeed.getPreviousPrice(stringToBytes32("ETH"), 0), "Negative price")
await expectRevert(priceFeed.getPreviousTimestamp(stringToBytes32("ETH"), 0), "Negative price")
const price = await priceFeed.getPreviousPrice(stringToBytes32("ETH"), 2)
expect(price).to.eq(toFullDigit(405))
const timestamp = await priceFeed.getPreviousTimestamp(stringToBytes32("ETH"), 2)
expect(timestamp).to.eq(150)
})
it("force error, getPreviousPrice/getPreviousTimestamp more than current history", async () => {
await expectRevert(priceFeed.getPreviousPrice(stringToBytes32("ETH"), 10), "Not enough history")
await expectRevert(priceFeed.getPreviousTimestamp(stringToBytes32("ETH"), 10), "Not enough history")
})
})
describe("when all price history are negative, there is no enough (valid) history", () => {
beforeEach(async () => {
await priceFeed.addAggregator(stringToBytes32("ETH"), chainlinkMock1.address)
await chainlinkMock1.mockAddAnswer(0, toFullDigit(-400, CHAINLINK_DECIMAL), 100, 100, 0)
await chainlinkMock1.mockAddAnswer(1, toFullDigit(-405, CHAINLINK_DECIMAL), 150, 150, 1)
await chainlinkMock1.mockAddAnswer(2, toFullDigit(-410, CHAINLINK_DECIMAL), 200, 200, 2)
})
it("force error, getTwapPrice", async () => {
await expectRevert(priceFeed.getTwapPrice(stringToBytes32("ETH"), 40), "Not enough history")
})
it("force error, getprice/getLatestTimestamp", async () => {
await expectRevert(priceFeed.getPrice(stringToBytes32("ETH")), "Not enough history")
await expectRevert(priceFeed.getLatestTimestamp(stringToBytes32("ETH")), "Not enough history")
})
it("force error, getPreviousPrice/getPreviousTimestamp still get the 'Negative price' error, as these two functions do not traverse back to a valid one", async () => {
await expectRevert(priceFeed.getPreviousPrice(stringToBytes32("ETH"), 0), "Negative price")
await expectRevert(priceFeed.getPreviousTimestamp(stringToBytes32("ETH"), 0), "Negative price")
await expectRevert(priceFeed.getPreviousPrice(stringToBytes32("ETH"), 1), "Negative price")
await expectRevert(priceFeed.getPreviousTimestamp(stringToBytes32("ETH"), 1), "Negative price")
})
})
})
Example #26
Source File: Permitable.test.ts From solidity-utils with MIT License | 4 votes |
contract('Permitable', function ([wallet1, wallet2]) {
const initContext = async () => {
const permittableMock = await PermitableMock.new();
const chainId = await web3.eth.getChainId();
const account = await web3.eth.accounts.create();
const wallet = {
address: account.address,
privateKey: account.privateKey,
};
const owner = wallet.address;
const holder = owner;
const erc20PermitMock: types.ERC20PermitMockInstance = undefined!;
const daiLikePermitMock: types.DaiLikePermitMockInstance = undefined!;
return { permittableMock, chainId, wallet, owner, holder, erc20PermitMock, daiLikePermitMock };
};
let context: Awaited<ReturnType<typeof initContext>> = undefined!;
before(async () => {
context = await initContext();
});
beforeEach(async function () {
context.erc20PermitMock = await ERC20PermitMock.new('USDC', 'USDC', wallet1, toBN(100));
context.daiLikePermitMock = await DaiLikePermitMock.new('DAI', 'DAI', wallet1, toBN(100));
});
it('should be permitted for IERC20Permit', async function () {
const permit = await getPermit(context.owner, context.wallet.privateKey, context.erc20PermitMock, '1', context.chainId, wallet2, value.toString());
await context.permittableMock.__permit(context.erc20PermitMock.address, permit);
expect(await context.erc20PermitMock.nonces(context.owner)).to.be.bignumber.equal('1');
expect(await context.erc20PermitMock.allowance(context.owner, wallet2)).to.be.bignumber.equal(value);
});
it('should not be permitted for IERC20Permit', async function () {
const data = buildData(await context.erc20PermitMock.name(), '1', context.chainId, context.erc20PermitMock.address, context.owner, wallet2, value.toString(), nonce);
const signature = signWithPk(context.wallet.privateKey, data);
const { v, r, s } = fromRpcSig(signature);
const permit = web3.eth.abi.encodeParameter(
'tuple(address,address,uint256,uint256,uint8,bytes32,bytes32)',
[context.owner, wallet1, value, defaultDeadline, v, r, s],
);
await expect(context.permittableMock.__permit(context.erc20PermitMock.address, permit))
.to.eventually.be.rejectedWith('ERC20Permit: invalid signature');
});
it('should be permitted for IDaiLikePermit', async function () {
const permit = await getPermitLikeDai(context.holder, context.wallet.privateKey, context.daiLikePermitMock, '1', context.chainId, wallet2, true);
await context.permittableMock.__permit(context.daiLikePermitMock.address, permit);
const MAX_UINT128 = toBN('2').pow(toBN('128')).sub(toBN('1'));
expect(await context.daiLikePermitMock.nonces(context.owner)).to.be.bignumber.equal('1');
expect(await context.daiLikePermitMock.allowance(context.owner, wallet2)).to.be.bignumber.equal(MAX_UINT128);
});
it('should not be permitted for IDaiLikePermit', async function () {
const data = buildDataLikeDai(await context.daiLikePermitMock.name(), '1', context.chainId, context.daiLikePermitMock.address, context.holder, wallet2, nonce, true);
const signature = signWithPk(context.wallet.privateKey, data);
const { v, r, s } = fromRpcSig(signature);
const payload = web3.eth.abi.encodeParameter(
'tuple(address,address,uint256,uint256,bool,uint8,bytes32,bytes32)',
[context.holder, wallet1, nonce, defaultDeadline, true, v, r, s],
);
await expect(context.permittableMock.__permit(context.daiLikePermitMock.address, payload))
.to.eventually.be.rejectedWith('Dai/invalid-permit');
});
it('should be wrong permit length', async function () {
const data = buildData(await context.erc20PermitMock.name(), '1', context.chainId, context.erc20PermitMock.address, context.owner, wallet2, value.toString(), nonce);
const signature = signWithPk(context.wallet.privateKey, data);
const { v, r, s } = fromRpcSig(signature);
const permit = web3.eth.abi.encodeParameter(
'tuple(address,uint256,uint256,uint8,bytes32,bytes32)',
[wallet2, value, defaultDeadline, v, r, s],
);
await expect(context.permittableMock.__permit(context.erc20PermitMock.address, permit))
.to.eventually.be.rejectedWith('BadPermitLength()');
});
});
Example #27
Source File: AmmSpec.ts From perpetual-protocol with GNU General Public License v3.0 | 4 votes |
describe("Amm Unit Test", () => {
const ETH_PRICE = 100
let amm: AmmFakeInstance
let priceFeed: L2PriceFeedMockInstance
let quoteToken: ERC20FakeInstance
let admin: string
let alice: string
let fundingPeriod: BN
let fundingBufferPeriod: BN
async function moveToNextBlocks(number: number = 1): Promise<void> {
const blockNumber = new BigNumber(await amm.mock_getCurrentBlockNumber())
await amm.mock_setBlockNumber(blockNumber.addn(number))
}
async function forward(seconds: number): Promise<void> {
const timestamp = new BigNumber(await amm.mock_getCurrentTimestamp())
await amm.mock_setBlockTimestamp(timestamp.addn(seconds))
const movedBlocks = seconds / 15 < 1 ? 1 : seconds / 15
await moveToNextBlocks(movedBlocks)
}
beforeEach(async () => {
const addresses = await web3.eth.getAccounts()
admin = addresses[0]
alice = addresses[1]
priceFeed = await deployL2MockPriceFeed(toFullDigit(ETH_PRICE))
quoteToken = await deployErc20Fake(toFullDigit(20000000))
amm = await deployAmm({
deployer: admin,
quoteAssetTokenAddr: quoteToken.address,
priceFeedAddr: priceFeed.address,
fluctuation: toFullDigit(0),
fundingPeriod: new BN(3600), // 1 hour
})
await amm.setCounterParty(admin)
fundingPeriod = await amm.fundingPeriod()
fundingBufferPeriod = await amm.fundingBufferPeriod()
})
describe("default value", () => {
it("updated after amm added", async () => {
const liquidityChangedSnapshot = await amm.getLiquidityChangedSnapshots(0)
expect(liquidityChangedSnapshot.quoteAssetReserve).eq(toFullDigit(1000))
expect(liquidityChangedSnapshot.baseAssetReserve).eq(toFullDigit(100))
expect(liquidityChangedSnapshot.cumulativeNotional).eq(0)
})
})
describe("setOpen", () => {
it("admin open amm", async () => {
await amm.setOpen(true)
expect(await amm.open()).is.true
})
it("init nextFundingTime is 0", async () => {
expect(await amm.nextFundingTime()).eq(0)
})
it("admin open amm will update nextFundingTime", async () => {
// given now: October 5, 2015 12:20:00 AM
const now = await amm.mock_getCurrentTimestamp()
expect(now).eq(1444004400)
// when amm open
await amm.setOpen(true)
// then nextFundingTime should be: October 5, 2015 1:00:00 AM
expect(await amm.nextFundingTime()).eq(1444006800)
})
it("admin close amm", async () => {
await amm.setOpen(true)
await amm.setOpen(false)
expect(await amm.open()).is.false
})
it("can't do almost everything when it's beginning", async () => {
const error = "amm was closed"
await expectRevert(amm.settleFunding({ from: admin }), error)
await expectRevert(amm.swapInput(Dir.ADD_TO_AMM, toDecimal(600), toDecimal(0), false), error)
await expectRevert(amm.swapOutput(Dir.ADD_TO_AMM, toDecimal(600), toDecimal(0)), error)
})
it("can't do almost everything when it's closed", async () => {
await amm.setOpen(true)
await amm.setOpen(false)
const error = "amm was closed"
await expectRevert(amm.settleFunding({ from: admin }), error)
await expectRevert(amm.swapInput(Dir.ADD_TO_AMM, toDecimal(600), toDecimal(0), false), error)
await expectRevert(amm.swapOutput(Dir.ADD_TO_AMM, toDecimal(600), toDecimal(0)), error)
})
it("force error: stranger close amm", async () => {
await expectRevert(amm.setOpen(false, { from: alice }), "PerpFiOwnableUpgrade: caller is not the owner")
})
})
describe("calculate fee/spread", () => {
it("calcFee", async () => {
// tx fee is 1%, spread is 1%
await amm.setTollRatio(toDecimal(0.01))
await amm.setSpreadRatio(toDecimal(0.01))
const fee = await amm.calcFee(toDecimal(10))
// [0] is tx fee, [1] is spread
expect(fee[0]).to.eq(toFullDigit(0.1))
expect(fee[1]).to.eq(toFullDigit(0.1))
})
it("set different fee ratio", async () => {
// tx fee is 10%, spread is 5%
await amm.setTollRatio(toDecimal(0.1))
await amm.setSpreadRatio(toDecimal(0.05))
const fee = await amm.calcFee(toDecimal(100))
expect(fee[0]).to.eq(toFullDigit(10))
expect(fee[1]).to.eq(toFullDigit(5))
})
it("set fee ratio to zero", async () => {
// tx fee is 0%, spread is 5%
await amm.setTollRatio(toDecimal(0))
await amm.setSpreadRatio(toDecimal(0.05))
const fee = await amm.calcFee(toDecimal(100))
expect(fee[0]).to.eq(toFullDigit(0))
expect(fee[1]).to.eq(toFullDigit(5))
})
it("calcFee with input `0` ", async () => {
const fee = await amm.calcFee(toDecimal(0))
expect(fee[0]).to.eq(toFullDigit(0))
expect(fee[1]).to.eq(toFullDigit(0))
})
it("force error, only owner can set fee/spread ratio", async () => {
const error = "PerpFiOwnableUpgrade: caller is not the owner"
await expectRevert(amm.setTollRatio(toDecimal(0.2), { from: alice }), error)
await expectRevert(amm.setSpreadRatio(toDecimal(0.2), { from: alice }), error)
})
})
describe("getInputPrice/getOutputPrice", () => {
beforeEach(async () => {
await amm.setOpen(true)
})
it("getInputPrice, add to amm ", async () => {
// amount = 100(quote asset reserved) - (100 * 1000) / (1000 + 50) = 4.7619...
// price = 50 / 4.7619 = 10.499
const amount = await amm.getInputPrice(Dir.ADD_TO_AMM, toDecimal(50))
expect(amount).to.eq("4761904761904761904")
})
it("getInputPrice, remove from amm ", async () => {
// amount = (100 * 1000) / (1000 - 50) - 100(quote asset reserved) = 5.2631578947368
// price = 50 / 5.263 = 9.5
const amount = await amm.getInputPrice(Dir.REMOVE_FROM_AMM, toDecimal(50))
expect(amount).to.eq("5263157894736842106")
})
it("getOutputPrice, add to amm ", async () => {
// amount = 1000(base asset reversed) - (100 * 1000) / (100 + 5) = 47.619047619047619048
// price = 47.619 / 5 = 9.52
const amount = await amm.getOutputPrice(Dir.ADD_TO_AMM, toDecimal(5))
expect(amount).to.eq("47619047619047619047")
})
it("getOutputPrice, add to amm with dividable output", async () => {
// a dividable number should not plus 1 at mantissa
const amount = await amm.getOutputPrice(Dir.ADD_TO_AMM, toDecimal(25))
expect(amount).to.eq(toFullDigit(200))
})
it("getOutputPrice, remove from amm ", async () => {
// amount = (100 * 1000) / (100 - 5) - 1000(base asset reversed) = 52.631578947368
// price = 52.631 / 5 = 10.52
const amount = await amm.getOutputPrice(Dir.REMOVE_FROM_AMM, toDecimal(5))
expect(amount).to.eq("52631578947368421053")
})
it("getOutputPrice, remove from amm with dividable output", async () => {
const amount = await amm.getOutputPrice(Dir.REMOVE_FROM_AMM, toDecimal(37.5))
expect(amount).to.eq(toFullDigit(600))
})
})
describe("swap", () => {
beforeEach(async () => {
await amm.setOpen(true)
})
it("swapInput, Long ", async () => {
// quote asset = (1000 * 100 / (1000 + 600 ))) - 100 = - 37.5
const receipt = await amm.swapInput(Dir.ADD_TO_AMM, toDecimal(600), toDecimal(0), false)
expectEvent(receipt, "SwapInput", {
dir: Dir.ADD_TO_AMM.toString(),
quoteAssetAmount: toFullDigit(600),
baseAssetAmount: toFullDigit(37.5),
})
expectEvent(receipt, "ReserveSnapshotted", {
quoteAssetReserve: toFullDigit(1600),
baseAssetReserve: toFullDigit(62.5),
})
expect(await amm.quoteAssetReserve()).to.eq(toFullDigit(1600))
expect(await amm.baseAssetReserve()).to.eq(toFullDigit(62.5))
})
it("swapInput, short ", async () => {
// quote asset = (1000 * 100 / (1000 - 600)) - 100 = 150
const receipt = await amm.swapInput(Dir.REMOVE_FROM_AMM, toDecimal(600), toDecimal(0), false)
expectEvent(receipt, "SwapInput", {
dir: Dir.REMOVE_FROM_AMM.toString(),
quoteAssetAmount: toFullDigit(600),
baseAssetAmount: toFullDigit(150),
})
expectEvent(receipt, "ReserveSnapshotted", {
quoteAssetReserve: toFullDigit(400),
baseAssetReserve: toFullDigit(250),
})
expect(await amm.quoteAssetReserve()).to.eq(toFullDigit(400))
expect(await amm.baseAssetReserve()).to.eq(toFullDigit(250))
})
it("swapOutput, short", async () => {
// base asset = 1000 - (1000 * 100 / (100 + 150)) = 600
const receipt = await amm.swapOutput(Dir.ADD_TO_AMM, toDecimal(150), toDecimal(0))
expectEvent(receipt, "SwapOutput", {
dir: Dir.ADD_TO_AMM.toString(),
quoteAssetAmount: toFullDigit(600),
baseAssetAmount: toFullDigit(150),
})
expect(await amm.quoteAssetReserve()).to.eq(toFullDigit(400))
expect(await amm.baseAssetReserve()).to.eq(toFullDigit(250))
})
it("swapOutput, long", async () => {
// base asset = (1000 * 100 / (100 - 50)) - 1000 = 1000
const receipt = await amm.swapOutput(Dir.REMOVE_FROM_AMM, toDecimal(50), toDecimal(0))
expectEvent(receipt, "SwapOutput", {
dir: Dir.REMOVE_FROM_AMM.toString(),
quoteAssetAmount: toFullDigit(1000),
baseAssetAmount: toFullDigit(50),
})
// baseAssetReserve = 1000 * 100 / (1000 + 800) = 55.555...
expect(await amm.quoteAssetReserve()).to.eq(toFullDigit(2000))
expect(await amm.baseAssetReserve()).to.eq(toFullDigit(50))
})
it("swapInput, short and then long", async () => {
// quote asset = (1000 * 100 / (1000 - 480) - 100 = 92.30769230769...
const response = await amm.swapInput(Dir.REMOVE_FROM_AMM, toDecimal(480), toDecimal(0), false)
expectEvent(response, "SwapInput", {
dir: Dir.REMOVE_FROM_AMM.toString(),
quoteAssetAmount: toFullDigit(480),
baseAssetAmount: "92307692307692307693",
})
expect(await amm.quoteAssetReserve()).to.eq(toFullDigit(520))
expect(await amm.baseAssetReserve()).to.eq("192307692307692307693")
// quote asset = 192.307 - (1000 * 100 / (520 + 960)) = 30.555...
const response2 = await amm.swapInput(Dir.ADD_TO_AMM, toDecimal(960), toDecimal(0), false)
expectEvent(response2, "SwapInput", {
dir: Dir.ADD_TO_AMM.toString(),
quoteAssetAmount: toFullDigit(960),
baseAssetAmount: "124740124740124740125",
})
// pTokenAfter = 250 - 3000/16 = 1000 / 16
expect(await amm.quoteAssetReserve()).to.eq(toFullDigit(1480))
expect(await amm.baseAssetReserve()).to.eq("67567567567567567568")
})
it("swapInput, short, long and long", async () => {
await amm.swapInput(Dir.REMOVE_FROM_AMM, toDecimal(200), toDecimal(0), false)
expect(await amm.quoteAssetReserve()).to.eq(toFullDigit(800))
expect(await amm.baseAssetReserve()).to.eq(toFullDigit(125))
// swapped base asset = 13.88...8
// base reserved = 125 - 13.88...8 = 111.11...2
await amm.swapInput(Dir.ADD_TO_AMM, toDecimal(100), toDecimal(0), false)
expect(await amm.quoteAssetReserve()).to.eq(toFullDigit(900))
expect(await amm.baseAssetReserve()).to.eq("111111111111111111112")
await amm.swapInput(Dir.ADD_TO_AMM, toDecimal(200), toDecimal(0), false)
expect(await amm.quoteAssetReserve()).to.eq(toFullDigit(1100))
expect(await amm.baseAssetReserve()).to.eq("90909090909090909092")
})
it("swapInput, short, long and short", async () => {
await amm.swapInput(Dir.REMOVE_FROM_AMM, toDecimal(200), toDecimal(25), false)
expect(await amm.quoteAssetReserve()).to.eq(toFullDigit(800))
expect(await amm.baseAssetReserve()).to.eq(toFullDigit(125))
// swapped base asset = 13.88...8
// base reserved = 125 - 13.88...8 = 111.11...2
await amm.swapInput(Dir.ADD_TO_AMM, toDecimal(450), toDecimal(45), false)
expect(await amm.quoteAssetReserve()).to.eq(toFullDigit(1250))
expect(await amm.baseAssetReserve()).to.eq(toFullDigit(80))
await amm.swapInput(Dir.REMOVE_FROM_AMM, toDecimal(250), toDecimal(20), false)
expect(await amm.quoteAssetReserve()).to.eq(toFullDigit(1000))
expect(await amm.baseAssetReserve()).to.eq(toFullDigit(100))
})
it("swapOutput, short and not dividable", async () => {
const amount = await amm.getOutputPrice(Dir.ADD_TO_AMM, toDecimal(5))
const receipt = await amm.swapOutput(Dir.ADD_TO_AMM, toDecimal(5), toDecimal(0))
expectEvent(receipt, "SwapOutput", {
dir: Dir.ADD_TO_AMM.toString(),
quoteAssetAmount: amount.d,
baseAssetAmount: toFullDigit(5),
})
})
it("swapOutput, long and not dividable", async () => {
const amount = await amm.getOutputPrice(Dir.REMOVE_FROM_AMM, toDecimal(5))
const receipt = await amm.swapOutput(Dir.REMOVE_FROM_AMM, toDecimal(5), toDecimal(0))
expectEvent(receipt, "SwapOutput", {
dir: Dir.REMOVE_FROM_AMM.toString(),
quoteAssetAmount: amount.d,
baseAssetAmount: toFullDigit(5),
})
})
it("swapOutput, long and then short the same size, should got different base asset amount", async () => {
// quote asset = (1000 * 100 / (100 - 10)) - 1000 = 111.111...2
const amount1 = await amm.getOutputPrice(Dir.REMOVE_FROM_AMM, toDecimal(10))
await amm.swapOutput(Dir.REMOVE_FROM_AMM, toDecimal(10), toDecimal(0))
expect(await amm.quoteAssetReserve()).to.eq("1111111111111111111112")
expect(await amm.baseAssetReserve()).to.eq(toFullDigit(90))
// quote asset = 1111.111 - (111.111 * 90 / (90 + 10)) = 111.11...1
// price will be 1 wei less after traded
const amount2 = await amm.getOutputPrice(Dir.ADD_TO_AMM, toDecimal(10))
expect(new BigNumber(amount1.d).sub(new BigNumber(amount2.d))).eq(1)
})
it("force error, swapInput, long but less than min base amount", async () => {
// long 600 should get 37.5 base asset, and reserves will be 1600:62.5
// but someone front run it, long 200 before the order 600/37.5
await amm.mockSetReserve(toDecimal(1250), toDecimal(80))
await expectRevert(
amm.swapInput(Dir.ADD_TO_AMM, toDecimal(600), toDecimal(37.5), false),
"Less than minimal base token",
)
})
it("force error, swapInput, short but more than min base amount", async () => {
// short 600 should get -150 base asset, and reserves will be 400:250
// but someone front run it, short 200 before the order 600/-150
await amm.mockSetReserve(toDecimal(800), toDecimal(125))
await expectRevert(
amm.swapInput(Dir.REMOVE_FROM_AMM, toDecimal(600), toDecimal(150), false),
"More than maximal base token",
)
})
describe("swapOutput & forceSwapOutput, slippage limits of swaps", () => {
beforeEach(async () => {
await amm.setOpen(true)
})
// 1250 - 1250 * 80 / (80 + 20) = 1250 - 1000 = 250
it("swapOutput, short", async () => {
await amm.mockSetReserve(toDecimal(1250), toDecimal(80))
const receipt = await amm.swapOutput(Dir.ADD_TO_AMM, toDecimal(20), toDecimal(100))
expectEvent(receipt, "SwapOutput", {
dir: Dir.ADD_TO_AMM.toString(),
quoteAssetAmount: toFullDigit(250),
baseAssetAmount: toFullDigit(20),
})
expect(await amm.quoteAssetReserve()).to.eq(toFullDigit(1000))
expect(await amm.baseAssetReserve()).to.eq(toFullDigit(100))
})
it("swapOutput, short, (amount should pay = 250) at the limit of min quote amount = 249", async () => {
await amm.mockSetReserve(toDecimal(1250), toDecimal(80))
const receipt = await amm.swapOutput(Dir.ADD_TO_AMM, toDecimal(20), toDecimal(249))
expectEvent(receipt, "SwapOutput", {
dir: Dir.ADD_TO_AMM.toString(),
quoteAssetAmount: toFullDigit(250),
baseAssetAmount: toFullDigit(20),
})
expect(await amm.quoteAssetReserve()).to.eq(toFullDigit(1000))
expect(await amm.baseAssetReserve()).to.eq(toFullDigit(100))
})
it("force error, swapOutput, short, less than min quote amount = 251", async () => {
await amm.mockSetReserve(toDecimal(1250), toDecimal(80))
await expectRevert(
amm.swapOutput(Dir.ADD_TO_AMM, toDecimal(20), toDecimal(251)),
"Less than minimal quote token",
)
})
it("force error, swapOutput, short, far less than min quote amount = 400", async () => {
await amm.mockSetReserve(toDecimal(1250), toDecimal(80))
await expectRevert(
amm.swapOutput(Dir.ADD_TO_AMM, toDecimal(20), toDecimal(400)),
"Less than minimal quote token",
)
})
// 800 * 125 / (125 - 25) - 800 = 1000 - 800 = 200
it("swapOutput, long", async () => {
await amm.mockSetReserve(toDecimal(800), toDecimal(125))
const receipt = await amm.swapOutput(Dir.REMOVE_FROM_AMM, toDecimal(25), toDecimal(400))
expectEvent(receipt, "SwapOutput", {
dir: Dir.REMOVE_FROM_AMM.toString(),
quoteAssetAmount: toFullDigit(200),
baseAssetAmount: toFullDigit(25),
})
expect(await amm.quoteAssetReserve()).to.eq(toFullDigit(1000))
expect(await amm.baseAssetReserve()).to.eq(toFullDigit(100))
})
it("swapOutput, long, (amount should pay = 200) at the limit of max quote amount = 201", async () => {
await amm.mockSetReserve(toDecimal(800), toDecimal(125))
const receipt = await amm.swapOutput(Dir.REMOVE_FROM_AMM, toDecimal(25), toDecimal(201))
expectEvent(receipt, "SwapOutput", {
dir: Dir.REMOVE_FROM_AMM.toString(),
quoteAssetAmount: toFullDigit(200),
baseAssetAmount: toFullDigit(25),
})
expect(await amm.quoteAssetReserve()).to.eq(toFullDigit(1000))
expect(await amm.baseAssetReserve()).to.eq(toFullDigit(100))
})
it("force error, swapOutput, long, more than max quote amount = 199", async () => {
// base asset =
await amm.mockSetReserve(toDecimal(800), toDecimal(125))
await expectRevert(
amm.swapOutput(Dir.REMOVE_FROM_AMM, toDecimal(25), toDecimal(199)),
"More than maximal quote token",
)
})
it("force error, swapOutput, long, far less more max quote amount = 100", async () => {
// base asset = (1000 * 100 / (100 - 50)) - 1000 = 1000
await amm.mockSetReserve(toDecimal(800), toDecimal(125))
await expectRevert(
amm.swapOutput(Dir.REMOVE_FROM_AMM, toDecimal(25), toDecimal(100)),
"More than maximal quote token",
)
})
})
})
describe("restrict price fluctuation", () => {
beforeEach(async () => {
await amm.setFluctuationLimitRatio(toDecimal(0.05))
await amm.setOpen(true)
await moveToNextBlocks()
})
it("swapInput, price goes up within the fluctuation limit", async () => {
// fluctuation is 5%, price is between 9.5 ~ 10.5
// BUY 24, reserve will be 1024 : 97.66, price is 1024 / 97.66 = 10.49
const receipt = await amm.swapInput(Dir.ADD_TO_AMM, toDecimal(24), toDecimal(0), false)
expectEvent(receipt, "SwapInput")
})
it("swapInput, price goes down within the fluctuation limit", async () => {
// fluctuation is 5%, price is between 9.5 ~ 10.5
// SELL 25, reserve will be 975 : 102.56, price is 975 / 102.56 = 9.51
const receipt = await amm.swapInput(Dir.REMOVE_FROM_AMM, toDecimal(25), toDecimal(0), false)
expectEvent(receipt, "SwapInput")
})
it("swapInput, price goes down, up and then down within the fluctuation limit", async () => {
// fluctuation is 5%, price is between 9.5 ~ 10.5
// SELL 25, reserve will be 975 : 102.56, price is 975 / 102.56 = 9.51
await amm.swapInput(Dir.REMOVE_FROM_AMM, toDecimal(25), toDecimal(0), false)
// BUY 49, reserve will be 1024 : 97.66, price is 1024 / 97.66 = 10.49
await amm.swapInput(Dir.ADD_TO_AMM, toDecimal(49), toDecimal(0), false)
// SELL 49, reserve will be 975 : 102.56, price is 975 / 102.56 = 9.51
const receipt = await amm.swapInput(Dir.REMOVE_FROM_AMM, toDecimal(49), toDecimal(0), false)
expectEvent(receipt, "SwapInput")
})
it("swapInput, price can go up and over the fluctuation limit once", async () => {
// fluctuation is 5%, price is between 9.5 ~ 10.5
// BUY 25, reserve will be 1025 : 97.56, price is 1025 / 97.56 = 10.50625
// but _canOverFluctuationLimit is true so it's ok to skip the check
const receipt = await amm.swapInput(Dir.ADD_TO_AMM, toDecimal(25), toDecimal(0), true)
expectEvent(receipt, "SwapInput")
})
it("swapOutput, price goes up within the fluctuation limit", async () => {
// fluctuation is 5%, price is between 9.5 ~ 10.5
// BUY 2.4 base, reserve will be 1024.6 : 97.6, price is 1024.6 / 97.6 = 10.5
const receipt = await amm.swapOutput(Dir.REMOVE_FROM_AMM, toDecimal(2.4), toDecimal(0))
expectEvent(receipt, "SwapOutput")
})
it("swapOutput, price goes down within the fluctuation limit", async () => {
// fluctuation is 5%, price is between 9.5 ~ 10.5
// SELL 2.5 base, reserve will be 975.6 : 102.5, price is 975.6 / 102.5 = 9.52
const receipt = await amm.swapOutput(Dir.ADD_TO_AMM, toDecimal(2.5), toDecimal(0))
expectEvent(receipt, "SwapOutput")
})
it("force error, swapInput, price goes up but cannot over the fluctuation limit", async () => {
// fluctuation is 5%, price is between 9.5 ~ 10.5
// BUY 25, reserve will be 1025 : 97.56, price is 1025 / 97.56 = 10.51
await expectRevert(
amm.swapInput(Dir.ADD_TO_AMM, toDecimal(25), toDecimal(0), false),
"price is over fluctuation limit",
)
})
it("force error, swapInput, price goes down but cannot over the fluctuation limit", async () => {
// fluctuation is 5%, price is between 9.5 ~ 10.5
// SELL 26, reserve will be 974 : 102.67, price is 974 / 102.67 = 9.49
await expectRevert(
amm.swapInput(Dir.REMOVE_FROM_AMM, toDecimal(26), toDecimal(0), false),
"price is over fluctuation limit",
)
})
it("force error, swapInput long can exceed the fluctuation limit once, but the rest will fail during that block", async () => {
// fluctuation is 5%, price is between 9.5 ~ 10.5
// BUY 25, reserve will be 1025 : 97.56, price is 1025 / 97.56 = 10.50625
// _canOverFluctuationLimit is true so it's ok to skip the check the first time, while the rest cannot
const receipt = await amm.swapInput(Dir.ADD_TO_AMM, toDecimal(25), toDecimal(0), true)
expectEvent(receipt, "SwapInput")
await expectRevert(
amm.swapInput(Dir.ADD_TO_AMM, toDecimal(1), toDecimal(0), true),
"price is already over fluctuation limit",
)
})
it("force error, swapInput short can exceed the fluctuation limit once, but the rest will fail during that block", async () => {
// fluctuation is 5%, price is between 9.5 ~ 10.5
// SELL 30, reserve will be 970 : 103.09, price is 975 / 102.56 = 9.40
// _canOverFluctuationLimit is true so it's ok to skip the check the first time, while the rest cannot
const receipt = await amm.swapInput(Dir.REMOVE_FROM_AMM, toDecimal(30), toDecimal(0), true)
expectEvent(receipt, "SwapInput")
await expectRevert(
amm.swapInput(Dir.REMOVE_FROM_AMM, toDecimal(1), toDecimal(0), true),
"price is already over fluctuation limit",
)
})
it("force error, swapOutput(close long) can exceed the fluctuation limit once, but the rest txs in that block will fail", async () => {
// fluctuation is 5%, price is between 9.5 ~ 10.5
// BUY 2.5 base, reserve will be 1025.6 : 97.5, price is 1025.6 / 97.5 = 10.52
expectEvent(await amm.swapOutput(Dir.REMOVE_FROM_AMM, toDecimal(2.5), toDecimal(0)), "SwapOutput")
await expectRevert(
amm.swapOutput(Dir.REMOVE_FROM_AMM, toDecimal(0.1), toDecimal(0)),
"price is already over fluctuation limit",
)
})
it("force error, swapOutput(close short) can only exceed fluctuation limit once, but the rest txs in that block will fail", async () => {
// fluctuation is 5%, price is between 9.5 ~ 10.5
// SELL 3 base, reserve will be 970.873 : 103, price is 970.873 / 103 = 9.425
expectEvent(await amm.swapOutput(Dir.ADD_TO_AMM, toDecimal(3), toDecimal(0)), "SwapOutput")
// SELL 3 base again, reserve will be 943.396 : 106, price is 970.873 / 106 = 8.899
await expectRevert(
amm.swapOutput(Dir.ADD_TO_AMM, toDecimal(3), toDecimal(0)),
"price is already over fluctuation limit",
)
})
it("force error, swapOutput(close short) can only exceed fluctuation limit once, but the rest txs in that block will fail, including the price comes inside the range", async () => {
// fluctuation is 5%, price is between 9.5 ~ 10.5
// SELL 3 base, reserve will be 970.873 : 103, price is 970.873 / 103 = 9.425
expectEvent(await amm.swapOutput(Dir.ADD_TO_AMM, toDecimal(3), toDecimal(0)), "SwapOutput")
// BUY 5 base again, reserve will be 1020.4081632653 : 98, price is 10.4123281966
await expectRevert(
amm.swapOutput(Dir.REMOVE_FROM_AMM, toDecimal(5), toDecimal(0)),
"price is already over fluctuation limit",
)
})
it("force error, swap many times and the price is over the fluctuation limit in a single block", async () => {
// fluctuation is 5%, price is between 9.5 ~ 10.5
// BUY 10+10+10, reserve will be 1030 : 97.09, price is 1030 / 97.09 = 10.61
await moveToNextBlocks(1)
await amm.swapInput(Dir.ADD_TO_AMM, toDecimal(10), toDecimal(0), false)
await amm.swapInput(Dir.ADD_TO_AMM, toDecimal(10), toDecimal(0), false)
await expectRevert(
amm.swapInput(Dir.ADD_TO_AMM, toDecimal(10), toDecimal(0), false),
"price is over fluctuation limit",
)
})
it("force error, compare price fluctuation with previous blocks in a block", async () => {
// BUY 10, reserve will be 1010 : 99.01, price is 1010 / 99.01 = 10.2
// fluctuation is 5%, price is between 9.69 ~ 10.71
await amm.swapInput(Dir.ADD_TO_AMM, toDecimal(10), toDecimal(0), false)
await moveToNextBlocks(1)
// SELL 26, reserve will be 984 : 101.63, price is 984 / 101.63 = 9.68
const error = "price is over fluctuation limit"
await expectRevert(amm.swapInput(Dir.REMOVE_FROM_AMM, toDecimal(26), toDecimal(0), false), error)
// BUY 30, reserve will be 1040 : 96.15, price is 1040 / 96.15 = 10.82
await expectRevert(amm.swapInput(Dir.ADD_TO_AMM, toDecimal(30), toDecimal(0), false), error)
// should revert as well if BUY 30 separately
await amm.swapInput(Dir.ADD_TO_AMM, toDecimal(10), toDecimal(0), false)
await expectRevert(amm.swapInput(Dir.ADD_TO_AMM, toDecimal(20), toDecimal(0), false), error)
})
it("force error, the value of fluctuation is the same even when no any tradings for blocks", async () => {
// BUY 10, reserve will be 1010 : 99.01, price is 1010 / 99.01 = 10.2
// fluctuation is 5%, price is between 9.69 ~ 10.71
await amm.swapInput(Dir.ADD_TO_AMM, toDecimal(10), toDecimal(0), false)
await moveToNextBlocks(3)
// BUY 25, reserve will be 1035 : 96.62, price is 1035 / 96.62 = 10.712
await expectRevert(
amm.swapInput(Dir.ADD_TO_AMM, toDecimal(25), toDecimal(0), false),
"price is over fluctuation limit",
)
})
})
describe("swapInput and swapOutput", () => {
beforeEach(async () => {
await amm.setOpen(true)
// avoid actions from exceeding the fluctuation limit
await amm.setFluctuationLimitRatio(toDecimal(0.5))
})
it("use getOutputPrice to query price and use it to swapInput(long)", async () => {
// when trader ask what's the requiredQuoteAsset if trader want to remove 10 baseAsset from amm
const requiredQuoteAsset = await amm.getOutputPrice(Dir.REMOVE_FROM_AMM, toDecimal(10))
// when trader add requiredQuoteAsset to amm
const receipt = await amm.swapInput(Dir.ADD_TO_AMM, requiredQuoteAsset, toDecimal(0), false)
// then event.baseAssetAmount should be equal to 10
expectEvent(receipt, "SwapInput", {
dir: Dir.ADD_TO_AMM.toString(),
quoteAssetAmount: requiredQuoteAsset.d,
baseAssetAmount: toFullDigit(10),
})
})
it("use getOutputPrice to query price and use it to swapInput(short)", async () => {
// when trader ask what's the requiredQuoteAsset if trader want to add 10 baseAsset from amm
const requiredQuoteAsset = await amm.getOutputPrice(Dir.ADD_TO_AMM, toDecimal(10))
// when trader remove requiredQuoteAsset to amm
const receipt = await amm.swapInput(Dir.REMOVE_FROM_AMM, requiredQuoteAsset, toDecimal(0), false)
// then event.baseAssetAmount should be equal to 10
expectEvent(receipt, "SwapInput", {
dir: Dir.REMOVE_FROM_AMM.toString(),
quoteAssetAmount: requiredQuoteAsset.d,
baseAssetAmount: toFullDigit(10),
})
})
it("use getInputPrice(long) to swapOutput", async () => {
// when trader ask what's the baseAsset she will receive if trader want to add 10 quoteAsset to amm
const receivedBaseAsset = await amm.getInputPrice(Dir.ADD_TO_AMM, toDecimal(10))
// when trader trade quoteAsset for receivedBaseAsset (amount as above)
const receipt = await amm.swapOutput(Dir.REMOVE_FROM_AMM, receivedBaseAsset, toDecimal(0))
// then event.quoteAsset should be equal to 10
// if swapOutput is adjusted, the price should be higher (>= 10)
expectEvent(receipt, "SwapOutput", {
dir: Dir.REMOVE_FROM_AMM.toString(),
quoteAssetAmount: toFullDigit(10),
baseAssetAmount: receivedBaseAsset.d,
})
})
it("use getInputPrice(short) to swapOutput", async () => {
// when trader ask what's the baseAsset she will receive if trader want to remove 10 quoteAsset to amm
const receivedBaseAsset = await amm.getInputPrice(Dir.REMOVE_FROM_AMM, toDecimal(10))
// when trader trade quoteAsset for receivedBaseAsset (amount as above)
const receipt = await amm.swapOutput(Dir.ADD_TO_AMM, receivedBaseAsset, toDecimal(0))
// then event.quoteAsset should be equal to 10
// if swapOutput is adjusted, the price should be higher (>= 10)
expectEvent(receipt, "SwapOutput", {
dir: Dir.ADD_TO_AMM.toString().toString(),
quoteAssetAmount: "10000000000000000009",
baseAssetAmount: receivedBaseAsset.d,
})
})
it("swapInput twice, short and long", async () => {
await amm.swapInput(Dir.REMOVE_FROM_AMM, toDecimal(10), toDecimal(0), false)
await amm.swapInput(Dir.ADD_TO_AMM, toDecimal(10), toDecimal(0), false)
// then the reserve shouldn't be less than the original reserve
expect(await amm.baseAssetReserve()).eq("100000000000000000001")
expect(await amm.quoteAssetReserve()).eq(toFullDigit(1000))
})
it("swapInput twice, long and short", async () => {
await amm.swapInput(Dir.ADD_TO_AMM, toDecimal(10), toDecimal(0), false)
await amm.swapInput(Dir.REMOVE_FROM_AMM, toDecimal(10), toDecimal(0), false)
// then the reserve shouldn't be less than the original reserve
expect(await amm.baseAssetReserve()).eq("100000000000000000001")
expect(await amm.quoteAssetReserve()).eq(toFullDigit(1000))
})
it("swapOutput twice, short and long", async () => {
await amm.swapOutput(Dir.REMOVE_FROM_AMM, toDecimal(10), toDecimal(0))
await amm.swapOutput(Dir.ADD_TO_AMM, toDecimal(10), toDecimal(0))
// then the reserve shouldn't be less than the original reserve
expect(await amm.baseAssetReserve()).eq(toFullDigit(100))
expect(await amm.quoteAssetReserve()).eq("1000000000000000000001")
})
it("swapOutput twice, long and short", async () => {
await amm.swapOutput(Dir.ADD_TO_AMM, toDecimal(10), toDecimal(0))
await amm.swapOutput(Dir.REMOVE_FROM_AMM, toDecimal(10), toDecimal(0))
// then the reserve shouldn't be less than the original reserve
expect(await amm.baseAssetReserve()).eq(toFullDigit(100))
expect(await amm.quoteAssetReserve()).eq("1000000000000000000001")
})
})
describe("twap price", () => {
beforeEach(async () => {
await amm.setOpen(true)
// Mainnet average block time is 13.6 secs, 14 is easier to calc
// create 30 snapshot first, the average price will be 9.04
await forward(14)
for (let i = 0; i < 30; i++) {
// console.log((await amm.getOutputPrice(Dir.ADD_TO_AMM, toDecimal(10))).d.toString())
if (i % 3 == 0) {
await amm.swapInput(Dir.REMOVE_FROM_AMM, toDecimal(100), toDecimal(0), false)
} else {
await amm.swapInput(Dir.ADD_TO_AMM, toDecimal(50), toDecimal(0), false)
}
await forward(14)
}
})
describe("Future twap price", () => {
// price will be only
// 8.12 (after REMOVE_FROM_AMM 100)
// 9.03 (after ADD_TO_AMM 50), and
// 10 (after the second ADD_TO_AMM 50)
// average is 9.04
it("get twap price", async () => {
// 210 / 14 = 15 snapshots,
// average is 9.04 =
// (8.12 x 5 snapshots x 14 secs + 9.03 x 5 x 14 + 10 x 5 x 14) / 210
const twap = await amm.getTwapPrice(210)
expect(twap).to.eq("9041666666666666665")
})
it("the timestamp of latest snapshot is now, the latest snapshot wont have any effect ", async () => {
// price is 8.12 but time weighted is zero
await amm.swapInput(Dir.REMOVE_FROM_AMM, toDecimal(100), toDecimal(0), false)
// 210 / 14 = 15 snapshots,
// average is 9.04 =
// (8.12 x 5 snapshots x 14 secs + 9.03 x 5 x 14 + 10 x 5 x 14) / 210
const twap = await amm.getTwapPrice(210)
expect(twap).to.eq("9041666666666666665")
})
it("asking interval more than snapshots have", async () => {
// only have 31 snapshots.
// average is 9.07 =
// (8.12 x 10 snapshots x 14 secs + 9.03 x 10 x 14 + 10 x 11 x 14) / (31 x 14))
expect(await amm.getTwapPrice(900)).to.eq("9072580645161290321")
})
it("asking interval less than latest snapshot, return latest price directly", async () => {
// price is 8.1
await amm.swapInput(Dir.REMOVE_FROM_AMM, toDecimal(100), toDecimal(0), false)
await forward(300)
expect(await amm.getTwapPrice(210)).to.eq("8099999999999999998")
})
it("price with interval 0 should be the same as spot price", async () => {
expect(await amm.getTwapPrice(0)).to.eq(await amm.getSpotPrice())
})
})
describe("Input asset twap price", () => {
describe("input twap", () => {
// price will be only
// 1221001221001221002 (after REMOVE_FROM_AMM 100)
// 1096491228070175439 (after ADD_TO_AMM 50), and
// 990099009900990099 (after the second ADD_TO_AMM 50)
it("get twap price", async () => {
// total snapshots will be 65, 65 x 14 = 910 secs
// getInputTwap/getOutputPrice get 15 mins average
for (let i = 0; i < 34; i++) {
if (i % 3 == 0) {
await amm.swapInput(Dir.REMOVE_FROM_AMM, toDecimal(100), toDecimal(0), false)
} else {
await amm.swapInput(Dir.ADD_TO_AMM, toDecimal(50), toDecimal(0), false)
}
await forward(14)
}
//
// average is 1103873668968336329 =
// (990099009900990099 x 21 snapshots x 14 secs + 1096491228070175439 x 21 x 14 + 1221001221001221002 x 22 x 14 +
// 990099009900990099 x 1 snapshots x 4 secs) / 900
const twap = await amm.getInputTwap(Dir.ADD_TO_AMM, toDecimal(10))
expect(twap).to.eq("1103873668968336329")
})
it("the timestamp of latest snapshot is now, the latest snapshot wont have any effect ", async () => {
// total snapshots will be 65, 65 x 14 = 910 secs
// getInputTwap/getOutputPrice get 15 mins average
for (let i = 0; i < 34; i++) {
if (i % 3 == 0) {
await amm.swapInput(Dir.REMOVE_FROM_AMM, toDecimal(100), toDecimal(0), false)
} else {
await amm.swapInput(Dir.ADD_TO_AMM, toDecimal(50), toDecimal(0), false)
}
await forward(14)
}
// price is 8.12 but time weighted is zero
await amm.swapInput(Dir.REMOVE_FROM_AMM, toDecimal(100), toDecimal(0), false)
const twap = await amm.getInputTwap(Dir.ADD_TO_AMM, toDecimal(10))
expect(twap).to.eq("1103873668968336329")
})
it("accumulative time of snapshots is less than 15 mins ", async () => {
// average is 1098903664504027596 =
// (990099009900990099 x 11 snapshots x 14 secs + 1096491228070175439 x 10 x 14 + 1221001221001221002 x 10 x 14) / (31 x 14)
const twap = await amm.getInputTwap(Dir.ADD_TO_AMM, toDecimal(10))
expect(twap).to.eq("1098903664504027596")
})
it("input asset is 0, should return 0", async () => {
const twap = await amm.getInputTwap(Dir.ADD_TO_AMM, toDecimal(0))
expect(twap).eq("0")
})
})
describe("output twap", () => {
// Output price will be only
// 74311926605504587146
// 82420091324200913231
// 90909090909090909079
it("get twap output price", async () => {
// total snapshots will be 65, 65 x 14 = 910 secs
// getInputTwap/getOutputPrice get 15 mins average
for (let i = 0; i < 34; i++) {
if (i % 3 == 0) {
await amm.swapInput(Dir.REMOVE_FROM_AMM, toDecimal(100), toDecimal(0), false)
} else {
await amm.swapInput(Dir.ADD_TO_AMM, toDecimal(50), toDecimal(0), false)
}
await forward(14)
}
//
// average is 82456099260799524707 =
// (90909090909090909079 x 21 snapshots x 14 secs + 82420091324200913231 x 21 x 14 + 74311926605504587146 x 22 x 14 +
// 90909090909090909079 x 1 snapshots x 4 secs) / 900
const twap = await amm.getOutputTwap(Dir.ADD_TO_AMM, toDecimal(10))
expect(twap).to.eq("82456099260799524707")
})
it("accumulative time of snapshots is less than 15 mins ", async () => {
// average is 82816779977324354961 =
// (90909090909090909079 x 11 snapshots x 14 secs + 82420091324200913231 x 10 x 14 + 74311926605504587146 x 10 x 14) / (31 x 14)
const twap = await amm.getOutputTwap(Dir.ADD_TO_AMM, toDecimal(10))
expect(twap).to.eq("82816779977324354961")
})
it("input asset is 0, should return 0", async () => {
const twap = await amm.getOutputTwap(Dir.ADD_TO_AMM, toDecimal(0))
expect(twap).eq("0")
})
})
})
})
describe("isOverSpreadLimit", () => {
beforeEach(async () => {
await amm.setOpen(true)
expect(await amm.getSpotPrice()).eq(toFullDigit(10))
})
it("will fail if price feed return 0", async () => {
await priceFeed.setPrice(0)
await expectRevert(amm.isOverSpreadLimit(), "underlying price is 0")
})
it("is true if abs((marketPrice-oraclePrice)/oraclePrice) >= 10%", async () => {
// (10-12)/12=0.16
await priceFeed.setPrice(toFullDigit(12))
expect(await amm.isOverSpreadLimit()).eq(true)
// (10-8)/8=0.25
await priceFeed.setPrice(toFullDigit(8))
expect(await amm.isOverSpreadLimit()).eq(true)
})
it("is false if abs((marketPrice-oraclePrice)/oraclePrice) < 10%", async () => {
// (10-10.5)/10.5=-0.04
await priceFeed.setPrice(toFullDigit(10.5))
expect(await amm.isOverSpreadLimit()).eq(false)
// (10-9.5)/9.5=0.05
await priceFeed.setPrice(toFullDigit(9.5))
expect(await amm.isOverSpreadLimit()).eq(false)
})
})
describe("AmmCalculator", () => {
beforeEach(async () => {
await amm.setOpen(true)
})
describe("getInputPriceWithReserves", () => {
it("should return 37.5B when ask for 600Q input at B100/Q1000 reserve and add to Amm", async () => {
const amount = await amm.getInputPriceWithReservesPublic(
Dir.ADD_TO_AMM,
toDecimal(600),
toDecimal(1000),
toDecimal(100),
)
expect(amount).eq(toFullDigit(37.5))
})
it("should return 150B when ask for 600Q input at B100/Q1000 reserve and remove from Amm", async () => {
const amount = await amm.getInputPriceWithReservesPublic(
Dir.REMOVE_FROM_AMM,
toDecimal(600),
toDecimal(1000),
toDecimal(100),
)
expect(amount).eq(toFullDigit(150))
})
it("should get expected (amount - 1) when the base asset amount is not dividable and add to Amm", async () => {
const amount = await amm.getInputPriceWithReservesPublic(
Dir.ADD_TO_AMM,
toDecimal(200),
toDecimal(1000),
toDecimal(100),
)
// 1000 * 100 / 1200 = 83.33
// 100 - 83.33 = 16.66..7 - 1
expect(amount).eq("16666666666666666666")
})
it("should get expected amount when the base asset amount is not dividable but remove from Amm", async () => {
const amount = await amm.getInputPriceWithReservesPublic(
Dir.REMOVE_FROM_AMM,
toDecimal(100),
toDecimal(1000),
toDecimal(100),
)
// trader will get 1 wei more negative position size
expect(amount).eq("11111111111111111112")
})
it("reach trading limit", async () => {
await expect(
amm.getInputPriceWithReservesPublic(
Dir.REMOVE_FROM_AMM,
toDecimal(900),
toDecimal(1000),
toDecimal(100),
),
).to.not.rejectedWith("over trading limit")
})
it("force error, value of quote asset is 0", async () => {
await expectRevert(
amm.getInputPriceWithReservesPublic(
Dir.REMOVE_FROM_AMM,
toDecimal(900),
toDecimal(900),
toDecimal(900),
),
"quote asset after is 0",
)
})
})
describe("getOutputPriceWithReserves", () => {
it("should need 375Q for 60B output at B100/Q1000 reserve when add to Amm", async () => {
const amount = await amm.getOutputPriceWithReservesPublic(
Dir.ADD_TO_AMM,
toDecimal(60),
toDecimal(1000),
toDecimal(100),
)
expect(amount).eq(toFullDigit(375).toString())
})
it("should need 250Q for 20B output at B100/Q1000 reserve when remove from Amm", async () => {
const amount = await amm.getOutputPriceWithReservesPublic(
Dir.REMOVE_FROM_AMM,
toDecimal(20),
toDecimal(1000),
toDecimal(100),
)
expect(amount).eq(toFullDigit(250).toString())
})
it("should get expected (amount + 1) when the quote asset amount is not dividable and remove Amm", async () => {
const amount = await amm.getOutputPriceWithReservesPublic(
Dir.REMOVE_FROM_AMM,
toDecimal(25),
toDecimal(1000),
toDecimal(100),
)
// 1000 * 100 / 75 = 1333.33
// 1333.33 - 1000 = 33.33...3 + 1
expect(amount).eq("333333333333333333334")
})
it("should get expected amount when the base asset amount is not dividable but add to Amm", async () => {
const amount = await amm.getOutputPriceWithReservesPublic(
Dir.ADD_TO_AMM,
toDecimal(20),
toDecimal(1000),
toDecimal(100),
)
// trader will get 1 wei less quoteAsset
expect(amount).eq("166666666666666666666")
})
it("force error, value of base asset is 0", async () => {
await expectRevert(
amm.getOutputPriceWithReservesPublic(
Dir.REMOVE_FROM_AMM,
toDecimal(900),
toDecimal(900),
toDecimal(900),
),
"base asset after is 0",
)
})
})
describe("the result of x's getOutPrice of getInputPrice should be equals to x", () => {
it("without fee, getOutputPrice(getInputPrice(x).amount) == x (quote settlement)", async () => {
const baseAssetAmount = await amm.getInputPriceWithReservesPublic(
Dir.ADD_TO_AMM,
toDecimal(250),
toDecimal(1000),
toDecimal(100),
)
const quoteAssetAmmPrice = await amm.getOutputPriceWithReservesPublic(
Dir.ADD_TO_AMM,
{ d: baseAssetAmount.toString() },
toDecimal(1250),
toDecimal(80),
)
expect(quoteAssetAmmPrice).eq(toFullDigit(250))
})
it("without fee, getOutputPrice(getInputPrice(x).amount) == x (base settlement)", async () => {
const baseAssetAmount = await amm.getInputPriceWithReservesPublic(
Dir.REMOVE_FROM_AMM,
toDecimal(200),
toDecimal(1000),
toDecimal(100),
)
const amount = await amm.getOutputPriceWithReservesPublic(
Dir.REMOVE_FROM_AMM,
{ d: baseAssetAmount.toString() },
toDecimal(800),
toDecimal(125),
)
expect(amount).eq(toFullDigit(200))
})
it("without fee, getInputPrice(getOutputPrice(x).amount) == x (quote settlement)", async () => {
const quoteAssetAmmPrice = await amm.getOutputPriceWithReservesPublic(
Dir.ADD_TO_AMM,
toDecimal(60),
toDecimal(1000),
toDecimal(100),
)
const baseAssetAmount = await amm.getInputPriceWithReservesPublic(
Dir.ADD_TO_AMM,
{ d: quoteAssetAmmPrice.toString() },
toDecimal(625),
toDecimal(160),
)
expect(baseAssetAmount).eq(toFullDigit(60))
})
it("without fee, getInputPrice(getOutputPrice(x).amount) == x (base settlement)", async () => {
const amount = await amm.getOutputPriceWithReservesPublic(
Dir.REMOVE_FROM_AMM,
toDecimal(60),
toDecimal(1000),
toDecimal(100),
)
const baseAssetAmount = await amm.getInputPriceWithReservesPublic(
Dir.REMOVE_FROM_AMM,
{ d: amount.toString() },
toDecimal(2500),
toDecimal(40),
)
expect(baseAssetAmount).eq(toFullDigit(60))
})
})
describe("AMM will always get 1 wei more reserve than trader when the result is not dividable", () => {
it("swapInput, add to amm", async () => {
// add 200 quote, amm: 83.33...4:1200. trader: 12.66
expect(
await amm.getInputPriceWithReservesPublic(
Dir.ADD_TO_AMM,
toDecimal(200),
toDecimal(1000),
toDecimal(100),
),
).eq("16666666666666666666")
})
it("swapInput, remove from amm", async () => {
// remove 100 quote, amm: 111.111...1 + 1 wei:900. trader: -11.11...1 - 1wei
expect(
await amm.getInputPriceWithReservesPublic(
Dir.REMOVE_FROM_AMM,
toDecimal(100),
toDecimal(1000),
toDecimal(100),
),
).eq("11111111111111111112")
})
it("swapOutput, add to amm", async () => {
// add 20 base, amm: 120:83.33...+ 1 wei. trader: 166.66..6
expect(
await amm.getOutputPriceWithReservesPublic(
Dir.ADD_TO_AMM,
toDecimal(20),
toDecimal(1000),
toDecimal(100),
),
).eq("166666666666666666666")
})
it("swapOutput, remove from amm", async () => {
// remove 10 base, amm: 90:1111.11...1 + 1 wei. trader: -111.11 - 1 wei
expect(
await amm.getOutputPriceWithReservesPublic(
Dir.REMOVE_FROM_AMM,
toDecimal(10),
toDecimal(1000),
toDecimal(100),
),
).eq("111111111111111111112")
})
})
describe("settleFunding", () => {
it("settleFunding delay before fundingBufferPeriod ends", async () => {
const originalNextFundingTime = await amm.nextFundingTime()
const settleFundingTimestamp = originalNextFundingTime.add(fundingBufferPeriod).subn(1)
await amm.mock_setBlockTimestamp(settleFundingTimestamp)
await amm.settleFunding()
expect(await amm.nextFundingTime()).eq(originalNextFundingTime.add(fundingPeriod))
})
it("settleFunding delay after fundingBufferPeriod ends & before nextFundingTime", async () => {
const originalNextFundingTime = await amm.nextFundingTime()
const settleFundingTimestamp = originalNextFundingTime.add(fundingBufferPeriod).addn(1)
await amm.mock_setBlockTimestamp(settleFundingTimestamp)
await amm.settleFunding()
expect(await amm.nextFundingTime()).eq(new BN(settleFundingTimestamp).add(fundingBufferPeriod))
})
it("force error, caller is not counterParty/clearingHouse", async () => {
const addresses = await web3.eth.getAccounts()
await expectRevert(amm.settleFunding({ from: addresses[1] }), "caller is not counterParty")
})
it("can't settleFunding multiple times at once even settleFunding delay", async () => {
const startAt = await amm.mock_getCurrentTimestamp()
const delayDuration = fundingPeriod.muln(10)
const settleFundingTimestamp = new BN(startAt).add(delayDuration)
await amm.mock_setBlockTimestamp(settleFundingTimestamp)
await amm.settleFunding()
await expectRevert(amm.settleFunding(), "settle funding too early")
})
})
})
})
Example #28
Source File: ClearingHouseTest_badDebt.ts From perpetual-protocol with GNU General Public License v3.0 | 4 votes |
describe("Bad Debt Test", () => {
let addresses: string[]
let admin: string
let whale: string
let shrimp: string
let amm: AmmFakeInstance
let insuranceFund: InsuranceFundFakeInstance
let quoteToken: ERC20FakeInstance
let mockPriceFeed!: L2PriceFeedMockInstance
let rewardsDistribution: RewardsDistributionFakeInstance
let clearingHouse: ClearingHouseFakeInstance
let supplySchedule: SupplyScheduleFakeInstance
async function forwardBlockTimestamp(time: number): Promise<void> {
const now = await supplySchedule.mock_getCurrentTimestamp()
const newTime = now.addn(time)
await rewardsDistribution.mock_setBlockTimestamp(newTime)
await amm.mock_setBlockTimestamp(newTime)
await supplySchedule.mock_setBlockTimestamp(newTime)
await clearingHouse.mock_setBlockTimestamp(newTime)
const movedBlocks = time / 15 < 1 ? 1 : time / 15
const blockNumber = new BigNumber(await amm.mock_getCurrentBlockNumber())
const newBlockNumber = blockNumber.addn(movedBlocks)
await rewardsDistribution.mock_setBlockNumber(newBlockNumber)
await amm.mock_setBlockNumber(newBlockNumber)
await supplySchedule.mock_setBlockNumber(newBlockNumber)
await clearingHouse.mock_setBlockNumber(newBlockNumber)
}
async function approve(account: string, spender: string, amount: number): Promise<void> {
await quoteToken.approve(spender, toFullDigit(amount, +(await quoteToken.decimals())), { from: account })
}
async function syncAmmPriceToOracle() {
const marketPrice = await amm.getSpotPrice()
await mockPriceFeed.setPrice(marketPrice.d)
}
beforeEach(async () => {
addresses = await web3.eth.getAccounts()
admin = addresses[0]
const contracts = await fullDeploy({ sender: admin })
amm = contracts.amm
insuranceFund = contracts.insuranceFund
quoteToken = contracts.quoteToken
mockPriceFeed = contracts.priceFeed
rewardsDistribution = contracts.rewardsDistribution
clearingHouse = contracts.clearingHouse
supplySchedule = contracts.supplySchedule
// for manipulating the price
whale = addresses[1]
await quoteToken.transfer(whale, toFullDigit(5000, +(await quoteToken.decimals())))
await approve(whale, clearingHouse.address, 5000)
// account that will incur bad debt
shrimp = addresses[2]
await quoteToken.transfer(shrimp, toFullDigit(15, +(await quoteToken.decimals())))
await approve(shrimp, clearingHouse.address, 15)
await quoteToken.transfer(insuranceFund.address, toFullDigit(50000, +(await quoteToken.decimals())))
await amm.setCap(toDecimal(0), toDecimal(0))
await syncAmmPriceToOracle()
// shrimp open small long
// position size: 7.40740741
await clearingHouse.openPosition(amm.address, Side.BUY, toDecimal(10), toDecimal(8), toDecimal(0), {
from: shrimp,
})
// whale drop spot price
for (let i = 0; i < 5; i++) {
await clearingHouse.openPosition(amm.address, Side.SELL, toDecimal(10), toDecimal(10), toDecimal(0), {
from: whale,
})
}
// spot price: 3.364
await forwardBlockTimestamp(1)
})
it("cannot increase position when bad debt", async () => {
// increase position should fail since margin is not enough
await expect(
clearingHouse.openPosition(amm.address, Side.BUY, toDecimal(10), toDecimal(10), toDecimal(0), {
from: shrimp,
}),
).to.be.revertedWith("Margin ratio not meet criteria")
// pump spot price
await clearingHouse.closePosition(amm.address, toDecimal(0), { from: whale })
// increase position should succeed since the position no longer has bad debt
await clearingHouse.openPosition(amm.address, Side.BUY, toDecimal(1), toDecimal(1), toDecimal(0), {
from: shrimp,
})
})
it("cannot reduce position when bad debt", async () => {
// reduce position should fail since margin is not enough
await expect(
clearingHouse.openPosition(amm.address, Side.SELL, toDecimal(1), toDecimal(1), toDecimal(0), {
from: shrimp,
}),
).to.be.revertedWith("Margin ratio not meet criteria")
// pump spot price
await clearingHouse.closePosition(amm.address, toDecimal(0), { from: whale })
// reduce position should succeed since the position no longer has bad debt
await clearingHouse.openPosition(amm.address, Side.SELL, toDecimal(1), toDecimal(1), toDecimal(0), {
from: shrimp,
})
})
it("cannot close position when bad debt", async () => {
// close position should fail since bad debt
// open notional = 80
// estimated realized PnL (partial close) = 7.4 * 3.36 - 80 = -55.136
// estimated remaining margin = 10 + (-55.136) = -45.136
// real bad debt: 46.10795455
await expect(
clearingHouse.closePosition(amm.address, toDecimal(0), {
from: shrimp,
}),
).to.be.revertedWith("bad debt")
// pump spot price
await clearingHouse.closePosition(amm.address, toDecimal(0), { from: whale })
// increase position should succeed since the position no longer has bad debt
await clearingHouse.closePosition(amm.address, toDecimal(0), {
from: shrimp,
})
})
it("can not partial close position when bad debt", async () => {
// set fluctuation limit ratio to trigger partial close
await amm.setFluctuationLimitRatio(toDecimal("0.000001"))
await clearingHouse.setPartialLiquidationRatio(toDecimal("0.25"))
// position size: 7.4074074074
// open notional = 80
// estimated realized PnL (partial close) = 7.4 * 0.25 * 3.36 - 80 * 0.25 = -13.784
// estimated remaining margin = 10 + (-13.784) = -3.784
// real bad debt = 4.027
await expect(
clearingHouse.closePosition(amm.address, toDecimal(0), {
from: shrimp,
}),
).to.be.revertedWith("bad debt")
})
it("can partial close position as long as it does not incur bad debt", async () => {
// set fluctuation limit ratio to trigger partial close
await amm.setFluctuationLimitRatio(toDecimal("0.000001"))
await clearingHouse.setPartialLiquidationRatio(toDecimal("0.1"))
// position size: 7.4074074074
// open notional = 80
// estimated realized PnL (partial close) = 7.4 * 0.1 * 3.36 - 80 * 0.1 = -5.5136
// estimated remaining margin = 10 + (-5.5136) = 4.4864
// real bad debt = 0
await clearingHouse.closePosition(amm.address, toDecimal(0), {
from: shrimp,
})
// remaining position size = 7.4074074074 * 0.9 = 6.66666667
expect((await clearingHouse.getPosition(amm.address, shrimp)).size).to.be.eq("6666666666666666667")
})
it("can liquidate position by backstop LP when bad debt", async () => {
// set whale to backstop LP
await clearingHouse.setBackstopLiquidityProvider(whale, true)
// close position should fail since bad debt
// open notional = 80
// estimated realized PnL (partial close) = 7.4 * 3.36 - 80 = -55.136
// estimated remaining margin = 10 + (-55.136) = -45.136
// real bad debt: 46.10795455
await expect(
clearingHouse.closePosition(amm.address, toDecimal(0), {
from: shrimp,
}),
).to.be.revertedWith("bad debt")
// no need to manipulate TWAP because the spot price movement is large enough
// that getMarginRatio() returns negative value already
await syncAmmPriceToOracle()
// can liquidate bad debt position
await clearingHouse.liquidate(amm.address, shrimp, {
from: whale,
})
})
it("cannot liquidate position by non backstop LP when bad debt", async () => {
// close position should fail since bad debt
// open notional = 80
// estimated realized PnL (partial close) = 7.4 * 3.36 - 80 = -55.136
// estimated remaining margin = 10 + (-55.136) = -45.136
// real bad debt: 46.10795455
await expect(
clearingHouse.closePosition(amm.address, toDecimal(0), {
from: shrimp,
}),
).to.be.revertedWith("bad debt")
// no need to manipulate TWAP because the spot price movement is large enough
// that getMarginRatio() returns negative value already
await syncAmmPriceToOracle()
// can liquidate bad debt position
await expect(
clearingHouse.liquidate(amm.address, shrimp, {
from: whale,
}),
).to.be.revertedWith("not backstop LP")
})
})
Example #29
Source File: ClearingHouse.add_removeMargin.test.ts From perpetual-protocol with GNU General Public License v3.0 | 4 votes |
describe("ClearingHouse add/remove margin Test", () => {
let addresses: string[]
let admin: string
let alice: string
let bob: string
let amm: AmmFakeInstance
let insuranceFund: InsuranceFundFakeInstance
let quoteToken: ERC20FakeInstance
let mockPriceFeed!: L2PriceFeedMockInstance
let rewardsDistribution: RewardsDistributionFakeInstance
let clearingHouse: ClearingHouseFakeInstance
let clearingHouseViewer: ClearingHouseViewerInstance
let supplySchedule: SupplyScheduleFakeInstance
async function gotoNextFundingTime(): Promise<void> {
const nextFundingTime = await amm.nextFundingTime()
await amm.mock_setBlockTimestamp(nextFundingTime)
}
async function forwardBlockTimestamp(time: number): Promise<void> {
const now = await supplySchedule.mock_getCurrentTimestamp()
const newTime = now.addn(time)
await rewardsDistribution.mock_setBlockTimestamp(newTime)
await amm.mock_setBlockTimestamp(newTime)
await supplySchedule.mock_setBlockTimestamp(newTime)
await clearingHouse.mock_setBlockTimestamp(newTime)
const movedBlocks = time / 15 < 1 ? 1 : time / 15
const blockNumber = new BigNumber(await amm.mock_getCurrentBlockNumber())
const newBlockNumber = blockNumber.addn(movedBlocks)
await rewardsDistribution.mock_setBlockNumber(newBlockNumber)
await amm.mock_setBlockNumber(newBlockNumber)
await supplySchedule.mock_setBlockNumber(newBlockNumber)
await clearingHouse.mock_setBlockNumber(newBlockNumber)
}
async function approve(account: string, spender: string, amount: number): Promise<void> {
await quoteToken.approve(spender, toFullDigit(amount, +(await quoteToken.decimals())), { from: account })
}
async function transfer(from: string, to: string, amount: number): Promise<void> {
await quoteToken.transfer(to, toFullDigit(amount, +(await quoteToken.decimals())), { from })
}
async function syncAmmPriceToOracle() {
const marketPrice = await amm.getSpotPrice()
await mockPriceFeed.setPrice(marketPrice.d)
}
beforeEach(async () => {
addresses = await web3.eth.getAccounts()
admin = addresses[0]
alice = addresses[1]
bob = addresses[2]
const contracts = await fullDeploy({ sender: admin })
amm = contracts.amm
insuranceFund = contracts.insuranceFund
quoteToken = contracts.quoteToken
mockPriceFeed = contracts.priceFeed
rewardsDistribution = contracts.rewardsDistribution
clearingHouse = contracts.clearingHouse
clearingHouseViewer = contracts.clearingHouseViewer
supplySchedule = contracts.supplySchedule
clearingHouse = contracts.clearingHouse
// Each of Alice & Bob have 5000 DAI
await quoteToken.transfer(alice, toFullDigit(5000, +(await quoteToken.decimals())))
await quoteToken.transfer(bob, toFullDigit(5000, +(await quoteToken.decimals())))
await quoteToken.transfer(insuranceFund.address, toFullDigit(5000, +(await quoteToken.decimals())))
await amm.setCap(toDecimal(0), toDecimal(0))
await syncAmmPriceToOracle()
})
describe("add/remove margin", () => {
beforeEach(async () => {
await approve(alice, clearingHouse.address, 2000)
await approve(bob, clearingHouse.address, 2000)
await clearingHouse.openPosition(amm.address, Side.BUY, toDecimal(60), toDecimal(10), toDecimal(37.5), {
from: alice,
})
})
it("add margin", async () => {
const receipt = await clearingHouse.addMargin(amm.address, toDecimal(80), { from: alice })
await expectEvent.inTransaction(receipt.tx, clearingHouse, "MarginChanged", {
sender: alice,
amm: amm.address,
amount: toFullDigit(80),
fundingPayment: "0",
})
await expectEvent.inTransaction(receipt.tx, quoteToken, "Transfer", {
from: alice,
to: clearingHouse.address,
value: toFullDigit(80, +(await quoteToken.decimals())),
})
expect((await clearingHouse.getPosition(amm.address, alice)).margin).to.eq(toFullDigit(140))
expect(await clearingHouseViewer.getPersonalBalanceWithFundingPayment(quoteToken.address, alice)).to.eq(
toFullDigit(140),
)
})
it("add margin even if there is no position opened yet", async () => {
const r = await clearingHouse.addMargin(amm.address, toDecimal(1), { from: bob })
expectEvent.inTransaction(r.tx, clearingHouse, "MarginChanged")
})
it("remove margin", async () => {
// remove margin 20
const receipt = await clearingHouse.removeMargin(amm.address, toDecimal(20), {
from: alice,
})
await expectEvent.inTransaction(receipt.tx, clearingHouse, "MarginChanged", {
sender: alice,
amm: amm.address,
amount: toFullDigit(-20),
fundingPayment: "0",
})
await expectEvent.inTransaction(receipt.tx, quoteToken, "Transfer", {
from: clearingHouse.address,
to: alice,
value: toFullDigit(20, +(await quoteToken.decimals())),
})
// 60 - 20
expect((await clearingHouse.getPosition(amm.address, alice)).margin).to.eq(toFullDigit(40))
// 60 - 20
expect(await clearingHouseViewer.getPersonalBalanceWithFundingPayment(quoteToken.address, alice)).to.eq(
toFullDigit(40),
)
})
it("remove margin after pay funding", async () => {
// given the underlying twap price is 25.5, and current snapShot price is 1600 / 62.5 = 25.6
await mockPriceFeed.setTwapPrice(toFullDigit(25.5))
// when the new fundingRate is 10% which means underlyingPrice < snapshotPrice
await gotoNextFundingTime()
await clearingHouse.payFunding(amm.address)
expect(await clearingHouse.getLatestCumulativePremiumFraction(amm.address)).eq(toFullDigit(0.1))
// remove margin 20
const receipt = await clearingHouse.removeMargin(amm.address, toDecimal(20), {
from: alice,
})
await expectEvent.inTransaction(receipt.tx, clearingHouse, "MarginChanged", {
sender: alice,
amm: amm.address,
amount: toFullDigit(-20),
fundingPayment: toFullDigit(3.75),
})
})
it("remove margin - no position opened yet but there is margin (edge case)", async () => {
await clearingHouse.addMargin(amm.address, toDecimal(1), { from: bob })
const receipt = await clearingHouse.removeMargin(amm.address, toDecimal(1), {
from: bob,
})
await expectEvent.inTransaction(receipt.tx, clearingHouse, "MarginChanged", {
sender: bob,
amm: amm.address,
amount: toFullDigit(-1),
fundingPayment: "0",
})
})
it("force error, remove margin - no enough margin", async () => {
// margin is 60, try to remove more than 60
const removedMargin = 61
await expectRevert(
clearingHouse.removeMargin(amm.address, toDecimal(removedMargin), { from: alice }),
"margin is not enough",
)
})
it("force error, remove margin - no enough margin ratio (4%)", async () => {
const removedMargin = 36
// min(margin + funding, margin + funding + unrealized PnL) - position value * 10%
// min(60 - 36, 60 - 36) - 600 * 0.1 = -24
// remove margin 36
// remain margin -> 60 - 36 = 24
// margin ratio -> 24 / 600 = 4%
await expectRevert(
clearingHouse.removeMargin(amm.address, toDecimal(removedMargin), { from: alice }),
"free collateral is not enough",
)
})
it("force error, remove margin - no position opened yet and neither is there any margin", async () => {
await expectRevert(
clearingHouse.removeMargin(amm.address, toDecimal(1), { from: bob }),
"margin is not enough",
)
})
})
describe("remove margin with unrealized PnL", () => {
beforeEach(async () => {
await approve(alice, clearingHouse.address, 2000)
await approve(bob, clearingHouse.address, 2000)
})
describe("using spot price", () => {
it("remove margin when a long position with profit", async () => {
// reserve 1000 : 100
await clearingHouse.openPosition(amm.address, Side.BUY, toDecimal(60), toDecimal(5), toDecimal(0), {
from: alice,
})
// reserve 1300 : 76.92, price = 16.9
await clearingHouse.openPosition(amm.address, Side.BUY, toDecimal(60), toDecimal(5), toDecimal(0), {
from: bob,
})
// reserve 1600 : 62.5, price = 25.6
// margin: 60
// positionSize: 23.08
// positionNotional: 431.5026875438
// unrealizedPnl: 431.5026875438 - 300 = 131.5026875438
// min(margin + funding, margin + funding + unrealized PnL) - position value * 5%
// min(60, 60 + 131.5026875438) - 300 * 0.05 = 42
// can not remove margin > 45
await expectRevert(
clearingHouse.removeMargin(amm.address, toDecimal(45.01), { from: alice }),
"free collateral is not enough",
)
const freeCollateral = await clearingHouseViewer.getFreeCollateral(amm.address, alice)
expect(freeCollateral).to.eq(toFullDigit(45))
await clearingHouse.removeMargin(amm.address, freeCollateral, { from: alice })
})
it("remove margin when a long position with loss", async () => {
await clearingHouse.openPosition(amm.address, Side.BUY, toDecimal(60), toDecimal(5), toDecimal(0), {
from: alice,
})
// reserve 1300 : 76.92, price = 16.9
await clearingHouse.openPosition(amm.address, Side.SELL, toDecimal(10), toDecimal(5), toDecimal(0), {
from: bob,
})
// reserve 1250 : 80 price = 15.625
// margin: 60
// positionSize: 23.08
// positionNotional: 279.88
// unrealizedPnl: 279.88 - 300 = -20.12
// min(margin + funding, margin + funding + unrealized PnL) - position value * 5%
// min(60, 60 + (-20.12)) - 300 * 0.05 = 24.88
// can not remove margin > 24.88
await expectRevert(
clearingHouse.removeMargin(amm.address, toDecimal(24.9), { from: alice }),
"free collateral is not enough",
)
const freeCollateral = await clearingHouseViewer.getFreeCollateral(amm.address, alice)
expect(freeCollateral).to.eq("24850746268656716414")
await clearingHouse.removeMargin(amm.address, freeCollateral, { from: alice })
})
it("remove margin when a short position with profit", async () => {
await clearingHouse.openPosition(amm.address, Side.SELL, toDecimal(20), toDecimal(5), toDecimal(0), {
from: alice,
})
// reserve 900 : 111.11, price = 8.1
await clearingHouse.openPosition(amm.address, Side.SELL, toDecimal(20), toDecimal(5), toDecimal(0), {
from: bob,
})
// reserve 800 : 125, price = 6.4
// margin: 20
// positionSize: -11.11
// positionNotional: 78.04
// unrealizedPnl: 100 - 78.04 = 21.96
// min(margin + funding, margin + funding + unrealized PnL) - position value * 5%
// min(20, 20 + 21.96) - 78.04 * 0.05 = 16.098
// can not remove margin > 16.098
await expectRevert(
clearingHouse.removeMargin(amm.address, toDecimal(16.5), { from: alice }),
"free collateral is not enough",
)
const freeCollateral = await clearingHouseViewer.getFreeCollateral(amm.address, alice)
expect(freeCollateral).to.eq("16097560975609756098")
await clearingHouse.removeMargin(amm.address, freeCollateral, { from: alice })
})
it("remove margin when a short position with loss", async () => {
await clearingHouse.openPosition(amm.address, Side.SELL, toDecimal(20), toDecimal(5), toDecimal(0), {
from: alice,
})
await clearingHouse.openPosition(amm.address, Side.BUY, toDecimal(10), toDecimal(5), toDecimal(0), {
from: bob,
})
// reserve 800 : 125, price = 6.4
// margin: 20
// positionSize: -11.11
// positionNotional: 112.1
// unrealizedPnl: 100 - 112.1 = -12.1
// min(margin + funding, margin + funding + unrealized PnL) - position value * 5%
// min(20, 20 + (-12.1)) - 112.1 * 0.05 = 2.295
// can not remove margin > 2.295
await expectRevert(
clearingHouse.removeMargin(amm.address, toDecimal(2.5), { from: alice }),
"free collateral is not enough",
)
const freeCollateral = await clearingHouseViewer.getFreeCollateral(amm.address, alice)
expect(freeCollateral).to.eq("2282608695652173905")
await clearingHouse.removeMargin(amm.address, freeCollateral, { from: alice })
})
})
describe("using twap", () => {
it("remove margin when a long position with profit", async () => {
// reserve 1000 : 100
await clearingHouse.openPosition(amm.address, Side.BUY, toDecimal(60), toDecimal(5), toDecimal(0), {
from: alice,
})
await forwardBlockTimestamp(450)
// reserve 1300 : 76.92, price = 16.9
await clearingHouse.openPosition(amm.address, Side.BUY, toDecimal(60), toDecimal(5), toDecimal(0), {
from: bob,
})
await forwardBlockTimestamp(450)
// reserve 1600 : 62.5, price = 25.6
// margin: 60
// positionSize: 23.08
// positionNotional: (300 + 431.5) / 2 = 365.75
// unrealizedPnl: 365.75 - 300 = 65.75
// min(margin + funding, margin + funding + unrealized PnL) - position value * 5%
// min(60, 60 + 65.75) - 300 * 0.05 = 45
// can not remove margin > 45
await expectRevert(
clearingHouse.removeMargin(amm.address, toDecimal(45.01), { from: alice }),
"free collateral is not enough",
)
await clearingHouse.removeMargin(amm.address, toDecimal(45), { from: alice })
})
it("remove margin when a long position with loss", async () => {
await clearingHouse.openPosition(amm.address, Side.BUY, toDecimal(60), toDecimal(5), toDecimal(0), {
from: alice,
})
await forwardBlockTimestamp(450)
// reserve 1300 : 76.92, price = 16.9
await clearingHouse.openPosition(amm.address, Side.SELL, toDecimal(10), toDecimal(5), toDecimal(0), {
from: bob,
})
await forwardBlockTimestamp(450)
// reserve 1250 : 80 price = 15.625
// push the price up, so that CH uses twap to calculate the loss
await clearingHouse.closePosition(amm.address, toDecimal(0), { from: bob })
// margin: 60
// positionSize: 23.08
// positionNotional: (300 + 279.85) / 2 = 289.925
// unrealizedPnl: 289.925 - 300 = -10.075
// min(margin + funding, margin + funding + unrealized PnL) - position value * 5%
// min(60, 60 + (-10.075)) - 300 * 0.05 = 34.925
// can not remove margin > 34.925
await expectRevert(
clearingHouse.removeMargin(amm.address, toDecimal(34.93), { from: alice }),
"free collateral is not enough",
)
const freeCollateral = await clearingHouseViewer.getFreeCollateral(amm.address, alice)
await clearingHouse.removeMargin(amm.address, toDecimal(34.92), { from: alice })
})
it("remove margin when a short position with profit", async () => {
await clearingHouse.openPosition(amm.address, Side.SELL, toDecimal(20), toDecimal(5), toDecimal(0), {
from: alice,
})
await forwardBlockTimestamp(450)
// reserve 900 : 111.11, price = 8.1
await clearingHouse.openPosition(amm.address, Side.SELL, toDecimal(20), toDecimal(5), toDecimal(0), {
from: bob,
})
await forwardBlockTimestamp(450)
// reserve 800 : 125, price = 6.4
// margin: 20
// positionSize: -11.11
// positionNotional: (78.04 + 100) / 2 = 89.02
// unrealizedPnl: 100 - 89.02 = 10.98
// min(margin + funding, margin + funding + unrealized PnL) - position value * 5%
// min(20, 20 + 10.98) - 89.02 * 0.05 = 15.549
// can not remove margin > 15.549
await expectRevert(
clearingHouse.removeMargin(amm.address, toDecimal(15.6), { from: alice }),
"free collateral is not enough",
)
await clearingHouse.removeMargin(amm.address, toDecimal(15.5), { from: alice })
})
it("remove margin when a short position with loss", async () => {
await clearingHouse.openPosition(amm.address, Side.SELL, toDecimal(20), toDecimal(5), toDecimal(0), {
from: alice,
})
await forwardBlockTimestamp(450)
await clearingHouse.openPosition(amm.address, Side.BUY, toDecimal(10), toDecimal(5), toDecimal(0), {
from: bob,
})
await forwardBlockTimestamp(450)
// reserve 800 : 125, price = 6.4
// pull the price down, so that CH uses twap to calculate the loss
await clearingHouse.closePosition(amm.address, toDecimal(0), { from: bob })
// margin: 20
// positionSize: -11.11
// positionNotional: (112.1 + 100) / 2 = 106.05
// unrealizedPnl: 100 - 106.05 = -6.05
// min(margin + funding, margin + funding + unrealized PnL) - position value * 5%
// min(20, 20 + (-6.05)) - 106.05 * 0.05 = 8.6475
// can not remove margin > 8.6475
await expectRevert(
clearingHouse.removeMargin(amm.address, toDecimal(8.7), { from: alice }),
"free collateral is not enough",
)
await clearingHouse.removeMargin(amm.address, toDecimal(8.6), { from: alice })
})
})
})
})