UNPKG

bnpl

Version:

The smart contracts for bnpl

587 lines (487 loc) 20.3 kB
import _ from 'lodash' import { solidity } from "ethereum-waffle" import chai from "chai" import { BigNumber, Signer, Wallet, constants, utils } from "ethers" import { ethers, deployments } from "hardhat" import { TestERC721, Conduit, BNPL, TestERC20, ERC4907, } from '../build/typechain' import { increaseTimestamp } from './testUtils' import { orderType } from '../eip-712-types/order' import { OrderComponents } from '../utils/types' import { calculateOrderHash } from '../utils/encoding' chai.use(solidity) const { expect } = chai const { get } = deployments describe("BoostPool", () => { let deployer: Wallet let alice: Wallet let bob: Wallet let cat: Wallet let attacker: Wallet let deployerAddress: string let aliceAddress: string let bobAddress: string let catAddress: string let conduit: Conduit let bnpl: BNPL let erc721: TestERC721 let erc20: TestERC20 let erc4907: ERC4907 let chainId: string let conduitKey: string const ERC721_TokenId = 100 const ERC4907_TokenId = 0 const setupTest = deployments.createFixture( async ({ deployments, ethers, getChainId }) => { await deployments.fixture() const signers = await ethers.getSigners(); [ deployer, alice, bob, cat, attacker ] = signers deployerAddress = await deployer.getAddress() aliceAddress = await alice.getAddress() bobAddress = await bob.getAddress() catAddress = await cat.getAddress() conduit = (await ethers.getContractAt( "Conduit", (await get("ConduitControllerCreateConduit")).address )) as Conduit bnpl = (await ethers.getContractAt( "BNPL", (await get("BNPL")).address )) as BNPL erc721 = (await ethers.getContractAt( "TestERC721", (await get("TestERC721")).address )) as TestERC721 erc20 = (await ethers.getContractAt( "TestERC20", (await get("TestERC20")).address )) as TestERC20 erc4907 = (await ethers.getContractAt( "ERC4907", (await get("ERC4907")).address )) as ERC4907 chainId = await getChainId() await erc721.mint(aliceAddress, ERC721_TokenId) await erc721.connect(alice).setApprovalForAll(conduit.address, true) await erc20.mint(aliceAddress, utils.parseEther('10000')) await erc20.mint(deployerAddress, utils.parseEther('10000')) await erc20.approve(conduit.address, constants.MaxUint256) conduitKey = `${deployerAddress}000000000000000000000000` } ) const getAndVerifyOrderHash = async (orderComponents: OrderComponents) => { const orderHash = await bnpl.getOrderHash(orderComponents) const derivedOrderHash = calculateOrderHash(orderComponents) expect(orderHash).to.equal(derivedOrderHash) return orderHash } const signOrder = async ( orderComponents: OrderComponents, signer: Wallet ) => { const domainData = { name: "BNPL", version: "1.0", chainId, verifyingContract: bnpl.address, } const signature = await signer._signTypedData( domainData, orderType, orderComponents ); const orderHash = await getAndVerifyOrderHash(orderComponents); const { domainSeparator } = await bnpl.information(); const digest = utils.keccak256( `0x1901${domainSeparator.slice(2)}${orderHash.slice(2)}` ); const recoveredAddress = utils.recoverAddress(digest, signature); expect(recoveredAddress).to.equal(signer.address); return signature; } const getETHOrderComponents = () => { const total = utils.parseEther('1.0') const royalty = total.mul(5).div(100) const fee = total.mul(1).div(100) const withdrawFee = total.mul(5).div(1000) return { offerer: aliceAddress, token: erc721.address, identifier: BigNumber.from(ERC721_TokenId), currency: constants.AddressZero, artist: catAddress, platform: bobAddress, startTime: 0, endTime: constants.MaxUint256, duration: 3600, periods: 3, amount: total, ratio: '7000', royalty: royalty, fee: fee, withdrawFee: withdrawFee, salt: constants.HashZero, conduitKey, counter: BigNumber.from(0) } } const getERC20OrderComponents = () => { const total = utils.parseEther('1.0') const royalty = total.mul(5).div(100) const fee = total.mul(1).div(100) const withdrawFee = total.mul(5).div(1000) return { offerer: aliceAddress, token: erc721.address, identifier: BigNumber.from(ERC721_TokenId), currency: erc20.address, artist: catAddress, platform: bobAddress, startTime: 0, endTime: constants.MaxUint256, duration: 3600, periods: 3, amount: total, ratio: '7000', royalty: royalty, fee: fee, withdrawFee: withdrawFee, salt: constants.HashZero, conduitKey, counter: BigNumber.from(0) } } beforeEach(async () => { await setupTest() }) describe("fulfillOrder", () => { it('signature', async () => { const orderComponents = getETHOrderComponents() await signOrder(orderComponents, alice) }) it('eth fulfillOrder', async () => { const orderComponents = getETHOrderComponents() const signature = await signOrder(orderComponents, alice) const aliceBalanceBefore = await alice.getBalance() const bobBalanceBefore = await bob.getBalance() const catBalanceBefore = await cat.getBalance() const orderHash = calculateOrderHash(orderComponents) let status = await bnpl.getOrderStatus(orderHash) expect(status.isValidated).eq(false) expect(status.isCancelled).eq(false) expect(status.isFinalized).eq(false) expect(status.isBroken).eq(false) expect(status.fulfiller).eq(constants.AddressZero) expect(status.startedAt).eq(0) expect(status.shadowId).eq(0) expect(status.paidTimes).eq(0) await bnpl.fulfillOrder({ parameters: orderComponents, signature }, conduitKey, { value: utils.parseEther('1.0') }) const toPlatform = orderComponents.fee.add(orderComponents.withdrawFee) const toOfferer = orderComponents.amount .div(orderComponents.periods) .mul(orderComponents.ratio) .div(10000) .sub(orderComponents.withdrawFee) .sub(orderComponents.royalty.div(3)) expect(await bob.getBalance()).eq(toPlatform.add(bobBalanceBefore)) expect(await cat.getBalance()).eq(catBalanceBefore) expect(await alice.getBalance()).eq(toOfferer.add(aliceBalanceBefore)) status = await bnpl.getOrderStatus(orderHash) expect(status.isValidated).eq(true) expect(status.isCancelled).eq(false) expect(status.isFinalized).eq(false) expect(status.isBroken).eq(false) expect(status.fulfiller).eq(deployerAddress) expect(status.startedAt).gt(0) expect(status.shadowId).eq(ERC4907_TokenId) expect(status.paidTimes).eq(1) expect(await erc4907.userOf(ERC4907_TokenId)).eq(deployerAddress) }) }) describe("repayOrder", () => { it('repay', async () => { const orderComponents = getETHOrderComponents() const orderHash = calculateOrderHash(orderComponents) const signature = await signOrder(orderComponents, alice) const aliceBalanceBefore = await alice.getBalance() const bobBalanceBefore = await bob.getBalance() const catBalanceBefore = await cat.getBalance() await bnpl.fulfillOrder({ parameters: orderComponents, signature }, conduitKey, { value: utils.parseEther('1.0') }) const toPlatform = orderComponents.fee.add(orderComponents.withdrawFee) const toOfferer = orderComponents.amount .div(orderComponents.periods) .mul(orderComponents.ratio) .div(10000) .sub(orderComponents.withdrawFee) .sub(orderComponents.royalty.div(3)) expect(await bob.getBalance()).eq(toPlatform.add(bobBalanceBefore)) expect(await cat.getBalance()).eq(catBalanceBefore) expect(await alice.getBalance()).eq(toOfferer.add(aliceBalanceBefore)) await bnpl.repayOrder( orderComponents, conduitKey, 1, { value: utils.parseEther('1.0') } ) const toPlatform2 = orderComponents.withdrawFee expect(await bob.getBalance()).eq(toPlatform.add(toPlatform2).add(bobBalanceBefore)) expect(await cat.getBalance()).eq(catBalanceBefore) expect(await alice.getBalance()).eq(toOfferer.mul(2).add(aliceBalanceBefore)) let status = await bnpl.getOrderStatus(orderHash) expect(status.isValidated).eq(true) expect(status.isCancelled).eq(false) expect(status.isFinalized).eq(false) expect(status.isBroken).eq(false) expect(status.fulfiller).eq(deployerAddress) expect(status.startedAt).gt(0) expect(status.shadowId).eq(ERC4907_TokenId) expect(status.paidTimes).eq(2) expect(await erc4907.userOf(ERC4907_TokenId)).eq(deployerAddress) expect(await erc721.ownerOf(ERC721_TokenId)).eq(bnpl.address) await bnpl.repayOrder( orderComponents, conduitKey, 1, { value: utils.parseEther('1.0') } ) expect(await bob.getBalance()).eq(toPlatform.add(toPlatform2.mul(2)).add(bobBalanceBefore)) expect(await cat.getBalance()).eq(orderComponents.royalty.add(catBalanceBefore)) expect(await alice.getBalance()).eq(orderComponents.amount.sub(orderComponents.withdrawFee.mul(3)).sub(orderComponents.royalty).add(aliceBalanceBefore)) status = await bnpl.getOrderStatus(orderHash) expect(status.isValidated).eq(true) expect(status.isCancelled).eq(false) expect(status.isFinalized).eq(true) expect(status.isBroken).eq(false) expect(status.fulfiller).eq(deployerAddress) expect(status.startedAt).gt(0) expect(status.shadowId).eq(ERC4907_TokenId) expect(status.paidTimes).eq(3) expect(await erc4907.userOf(ERC4907_TokenId)).eq(constants.AddressZero) expect(await erc721.ownerOf(ERC721_TokenId)).eq(deployerAddress) }) it('repay ERC20', async () => { const orderComponents = getERC20OrderComponents() const orderHash = calculateOrderHash(orderComponents) const signature = await signOrder(orderComponents, alice) const aliceBalanceBefore = await erc20.balanceOf(aliceAddress) const bobBalanceBefore = await erc20.balanceOf(bobAddress) const catBalanceBefore = await erc20.balanceOf(catAddress) await bnpl.fulfillOrder({ parameters: orderComponents, signature }, conduitKey) const toPlatform = orderComponents.fee.add(orderComponents.withdrawFee) const toOfferer = orderComponents.amount .div(orderComponents.periods) .mul(orderComponents.ratio) .div(10000) .sub(orderComponents.withdrawFee) .sub(orderComponents.royalty.div(3)) expect(await erc20.balanceOf(bobAddress)).eq(toPlatform.add(bobBalanceBefore)) expect(await erc20.balanceOf(catAddress)).eq(catBalanceBefore) expect(await erc20.balanceOf(aliceAddress)).eq(toOfferer.add(aliceBalanceBefore)) await bnpl.repayOrder( orderComponents, conduitKey, 1 ) const toPlatform2 = orderComponents.withdrawFee expect(await erc20.balanceOf(bobAddress)).eq(toPlatform.add(toPlatform2).add(bobBalanceBefore)) expect(await erc20.balanceOf(catAddress)).eq(catBalanceBefore) expect(await erc20.balanceOf(aliceAddress)).eq(toOfferer.mul(2).add(aliceBalanceBefore)) let status = await bnpl.getOrderStatus(orderHash) expect(status.isValidated).eq(true) expect(status.isCancelled).eq(false) expect(status.isFinalized).eq(false) expect(status.isBroken).eq(false) expect(status.fulfiller).eq(deployerAddress) expect(status.startedAt).gt(0) expect(status.shadowId).eq(ERC4907_TokenId) expect(status.paidTimes).eq(2) expect(await erc4907.userOf(ERC4907_TokenId)).eq(deployerAddress) expect(await erc721.ownerOf(ERC721_TokenId)).eq(bnpl.address) const tx = await bnpl.repayOrder( orderComponents, conduitKey, 1 ) const rx = await tx.wait() console.log(rx.gasUsed.toString()) expect(await erc20.balanceOf(bobAddress)).eq(toPlatform.add(toPlatform2.mul(2)).add(bobBalanceBefore)) expect(await erc20.balanceOf(catAddress)).eq(orderComponents.royalty.add(catBalanceBefore)) expect(await erc20.balanceOf(aliceAddress)).eq(orderComponents.amount.sub(orderComponents.withdrawFee.mul(3)).sub(orderComponents.royalty).add(aliceBalanceBefore)) status = await bnpl.getOrderStatus(orderHash) expect(status.isValidated).eq(true) expect(status.isCancelled).eq(false) expect(status.isFinalized).eq(true) expect(status.isBroken).eq(false) expect(status.fulfiller).eq(deployerAddress) expect(status.startedAt).gt(0) expect(status.shadowId).eq(ERC4907_TokenId) expect(status.paidTimes).eq(3) expect(await erc4907.userOf(ERC4907_TokenId)).eq(constants.AddressZero) expect(await erc721.ownerOf(ERC721_TokenId)).eq(deployerAddress) }) it('repay all', async () => { const orderComponents = getETHOrderComponents() const orderHash = calculateOrderHash(orderComponents) const signature = await signOrder(orderComponents, alice) const aliceBalanceBefore = await alice.getBalance() const bobBalanceBefore = await bob.getBalance() const catBalanceBefore = await cat.getBalance() await bnpl.fulfillOrder({ parameters: orderComponents, signature }, conduitKey, { value: utils.parseEther('1.0') }) await bnpl.repayOrder( orderComponents, conduitKey, 2, { value: utils.parseEther('1.0') } ) expect(await bob.getBalance()).eq(orderComponents.withdrawFee.mul(2).add(orderComponents.fee).add(bobBalanceBefore)) expect(await cat.getBalance()).eq(orderComponents.royalty.add(catBalanceBefore)) expect(await alice.getBalance()).eq(orderComponents.amount.sub(orderComponents.withdrawFee.mul(2)).sub(orderComponents.royalty).add(aliceBalanceBefore)) let status = await bnpl.getOrderStatus(orderHash) expect(status.isValidated).eq(true) expect(status.isCancelled).eq(false) expect(status.isFinalized).eq(true) expect(status.fulfiller).eq(deployerAddress) expect(status.startedAt).gt(0) expect(status.shadowId).eq(ERC4907_TokenId) expect(status.paidTimes).eq(3) expect(await erc721.ownerOf(100)).eq(deployerAddress) expect(await erc4907.userOf(ERC4907_TokenId)).eq(constants.AddressZero) }) it('repay all ERC20', async () => { const orderComponents = getERC20OrderComponents() const orderHash = calculateOrderHash(orderComponents) const signature = await signOrder(orderComponents, alice) const aliceBalanceBefore = await erc20.balanceOf(aliceAddress) const bobBalanceBefore = await erc20.balanceOf(bobAddress) const catBalanceBefore = await erc20.balanceOf(catAddress) await bnpl.fulfillOrder({ parameters: orderComponents, signature }, conduitKey) await bnpl.repayOrder( orderComponents, conduitKey, 2 ) expect(await erc20.balanceOf(bobAddress)).eq(orderComponents.withdrawFee.mul(2).add(orderComponents.fee).add(bobBalanceBefore)) expect(await erc20.balanceOf(catAddress)).eq(orderComponents.royalty.add(catBalanceBefore)) expect(await erc20.balanceOf(aliceAddress)).eq(orderComponents.amount.sub(orderComponents.withdrawFee.mul(2)).sub(orderComponents.royalty).add(aliceBalanceBefore)) let status = await bnpl.getOrderStatus(orderHash) expect(status.isValidated).eq(true) expect(status.isCancelled).eq(false) expect(status.isFinalized).eq(true) expect(status.isBroken).eq(false) expect(status.fulfiller).eq(deployerAddress) expect(status.startedAt).gt(0) expect(status.shadowId).eq(ERC4907_TokenId) expect(status.paidTimes).eq(3) expect(await erc721.ownerOf(100)).eq(deployerAddress) expect(await erc4907.userOf(ERC4907_TokenId)).eq(constants.AddressZero) }) }) describe("breakOrder", () => { it('break', async () => { const orderComponents = getETHOrderComponents() const orderHash = calculateOrderHash(orderComponents) const signature = await signOrder(orderComponents, alice) const aliceBalanceBefore = await alice.getBalance() const bobBalanceBefore = await bob.getBalance() const catBalanceBefore = await cat.getBalance() await bnpl.fulfillOrder({ parameters: orderComponents, signature }, conduitKey, { value: utils.parseEther('1.0') }) await increaseTimestamp(3601) await bnpl.breakOrder(orderComponents) const toOfferer = orderComponents.amount .div(orderComponents.periods) .mul(orderComponents.ratio) .div(10000) .sub(orderComponents.withdrawFee) const toPlatform = orderComponents.withdrawFee .add(orderComponents.fee) .add(orderComponents.amount.div(orderComponents.periods) .sub(orderComponents.amount .div(orderComponents.periods) .mul(orderComponents.ratio) .div(10000) ) ) expect(await bob.getBalance()).eq(toPlatform.add(bobBalanceBefore)) expect(await cat.getBalance()).eq(catBalanceBefore) expect(await alice.getBalance()).eq(toOfferer.add(aliceBalanceBefore)) const status = await bnpl.getOrderStatus(orderHash) expect(status.isValidated).eq(true) expect(status.isCancelled).eq(false) expect(status.isFinalized).eq(true) expect(status.isBroken).eq(true) expect(status.fulfiller).eq(deployerAddress) expect(status.startedAt).gt(0) expect(status.shadowId).eq(ERC4907_TokenId) expect(status.paidTimes).eq(1) expect(await erc4907.userOf(ERC4907_TokenId)).eq(constants.AddressZero) expect(await erc721.ownerOf(ERC721_TokenId)).eq(aliceAddress) }) it('break ERC20', async () => { const orderComponents = getERC20OrderComponents() const orderHash = calculateOrderHash(orderComponents) const signature = await signOrder(orderComponents, alice) const aliceBalanceBefore = await erc20.balanceOf(aliceAddress) const bobBalanceBefore = await erc20.balanceOf(bobAddress) const catBalanceBefore = await erc20.balanceOf(catAddress) await bnpl.fulfillOrder({ parameters: orderComponents, signature }, conduitKey) await increaseTimestamp(3601) await bnpl.breakOrder(orderComponents) const toOfferer = orderComponents.amount .div(orderComponents.periods) .mul(orderComponents.ratio) .div(10000) .sub(orderComponents.withdrawFee) const toPlatform = orderComponents.withdrawFee .add(orderComponents.fee) .add(orderComponents.amount.div(orderComponents.periods) .sub(orderComponents.amount .div(orderComponents.periods) .mul(orderComponents.ratio) .div(10000) ) ) expect(await erc20.balanceOf(bobAddress)).eq(toPlatform.add(bobBalanceBefore)) expect(await erc20.balanceOf(catAddress)).eq(catBalanceBefore) expect(await erc20.balanceOf(aliceAddress)).eq(toOfferer.add(aliceBalanceBefore)) const status = await bnpl.getOrderStatus(orderHash) expect(status.isValidated).eq(true) expect(status.isCancelled).eq(false) expect(status.isFinalized).eq(true) expect(status.isBroken).eq(true) expect(status.fulfiller).eq(deployerAddress) expect(status.startedAt).gt(0) expect(status.shadowId).eq(ERC4907_TokenId) expect(status.paidTimes).eq(1) expect(await erc4907.userOf(ERC4907_TokenId)).eq(constants.AddressZero) expect(await erc721.ownerOf(ERC721_TokenId)).eq(aliceAddress) }) }) describe("cancelOrder", () => { }) })