diff --git a/.gitignore b/.gitignore index 04527be..26bb100 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,5 @@ yarn-error.log* _archivepackage-lock.json examples/*.json + +.env \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index cd81a4f..863c1cd 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,7 +11,7 @@ "editor.wordWrap": "off", "editor.autoIndent": "keep", "editor.codeActionsOnSave": { - "source.fixAll.eslint": true + "source.fixAll.eslint": "explicit" }, "[jsonc]": { "editor.defaultFormatter": "esbenp.prettier-vscode" diff --git a/docker-compose.yml b/docker-compose.yml index f69f586..18592e8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,7 @@ version: '3.7' services: ajna-testnet: - image: ghcr.io/ajna-finance/ajna-testnet:rc9 + image: ghcr.io/builtbymom/ajna-testnet:rc10 ports: - 8555:8555 container_name: ajna-testnet-sdk diff --git a/examples/lend.ts b/examples/lend.ts index e7dc6a1..3c0c752 100755 --- a/examples/lend.ts +++ b/examples/lend.ts @@ -54,8 +54,13 @@ async function addLiquidity(amount: BigNumber, price: BigNumber) { throw new SdkError('Please provide a valid price'); const bucket = await pool.getBucketByPrice(price); - let tx = await pool.quoteApprove(signerLender, amount); + + let tx = await pool.quoteApproveHelper(signerLender, amount); + await tx.verifyAndSubmit(); + + tx = await pool.approveLenderHelperLPTransferor(signerLender); await tx.verifyAndSubmit(); + tx = await bucket.addQuoteToken(signerLender, amount); await tx.verifyAndSubmit(); console.log('Added', fromWad(amount), 'liquidity to bucket', bucket.index); diff --git a/package.json b/package.json index 67e5062..aa13c8c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@ajna-finance/sdk", "description": "A typescript SDK that can be used to create Dapps in Ajna ecosystem.", - "version": "0.3.5", + "version": "0.4.0", "repository": { "type": "git", "url": "https://github.com/ajna-finance/sdk.git" diff --git a/src/abis/AjnaLenderHelper.json b/src/abis/AjnaLenderHelper.json new file mode 100644 index 0000000..467189b --- /dev/null +++ b/src/abis/AjnaLenderHelper.json @@ -0,0 +1,176 @@ +[ + { + "inputs": [], + "name": "BucketIndexOutOfBounds", + "type": "error" + }, + { + "inputs": [], + "name": "InsufficientLP", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "int256", + "name": "x", + "type": "int256" + } + ], + "name": "PRBMathSD59x18__Exp2InputTooBig", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "int256", + "name": "x", + "type": "int256" + } + ], + "name": "PRBMathSD59x18__FromIntOverflow", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "int256", + "name": "x", + "type": "int256" + } + ], + "name": "PRBMathSD59x18__FromIntUnderflow", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "int256", + "name": "x", + "type": "int256" + } + ], + "name": "PRBMathSD59x18__LogInputTooSmall", + "type": "error" + }, + { + "inputs": [], + "name": "PRBMathSD59x18__MulInputTooSmall", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "rAbs", + "type": "uint256" + } + ], + "name": "PRBMathSD59x18__MulOverflow", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "prod1", + "type": "uint256" + } + ], + "name": "PRBMath__MulDivFixedPointOverflow", + "type": "error" + }, + { + "inputs": [], + "name": "RoundedAmountExceededRequestedMaximum", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool_", + "type": "address" + }, + { + "internalType": "uint256", + "name": "maxAmount_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "index_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expiry_", + "type": "uint256" + } + ], + "name": "addQuoteToken", + "outputs": [ + { + "internalType": "uint256", + "name": "bucketLP_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "addedAmount_", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool_", + "type": "address" + }, + { + "internalType": "uint256", + "name": "maxAmount_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "fromIndex_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "toIndex_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expiry_", + "type": "uint256" + } + ], + "name": "moveQuoteToken", + "outputs": [ + { + "internalType": "uint256", + "name": "fromBucketRedeemedLP_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "toBucketAwardedLP_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "movedAmount_", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/src/classes/Bucket.ts b/src/classes/Bucket.ts index 7be9b7e..6b71d23 100644 --- a/src/classes/Bucket.ts +++ b/src/classes/Bucket.ts @@ -1,24 +1,19 @@ import { BigNumber, Contract, Signer, constants } from 'ethers'; -import { MIN_BUCKET_LP, MAX_FENWICK_INDEX } from '../constants'; +import { MAX_FENWICK_INDEX } from '../constants'; import { multicall } from '../contracts/common'; -import { - addQuoteToken, - lenderInfo, - lenderKick, - moveQuoteToken, - removeQuoteToken, -} from '../contracts/pool'; +import { lenderInfo, lenderKick, removeQuoteToken } from '../contracts/pool'; import { bucketInfo, getPoolInfoUtilsContract, lpToCollateral, lpToQuoteTokens, } from '../contracts/pool-info-utils'; -import { Address, CallData, PoolInfoUtils, SdkError, SignerOrProvider } from '../types'; +import { Address, CallData, PoolInfoUtils, SignerOrProvider } from '../types'; import { fromWad, toWad } from '../utils/numeric'; import { indexToPrice } from '../utils/pricing'; import { getExpiry } from '../utils/time'; import { Pool } from './Pool'; +import { addQuoteToken, moveQuoteToken } from '../contracts/lender-helper'; export interface BucketStatus { /* amount of quote token, including accrued interest, owed to the bucket */ @@ -104,22 +99,6 @@ export class Bucket { }; } - /** - * Validates bucket LP balance to be greater than MIN_BUCKET_LP constant - * @returns true or throws SdkError - */ - validateLPBalance = async (index?: number) => { - const bucketStatus = await this.getStatus(index); - - if (!bucketStatus.bucketLP.isZero() && bucketStatus.bucketLP.lt(MIN_BUCKET_LP)) { - throw new SdkError( - 'You can’t deposit in this price bucket right now. Please try another one.' - ); - } - - return true; - }; - /** * Deposits quote token into the bucket. * @param signer lender @@ -128,12 +107,9 @@ export class Bucket { * @returns promise to transaction */ async addQuoteToken(signer: Signer, amount: BigNumber, ttlSeconds?: number) { - const contractPoolWithSigner = this.poolContract.connect(signer); - - await this.validateLPBalance(); - return addQuoteToken( - contractPoolWithSigner, + signer, + this.pool.poolAddress, amount, this.index, await getExpiry(this.provider, ttlSeconds) @@ -154,11 +130,9 @@ export class Bucket { maxAmountToMove = constants.MaxUint256, ttlSeconds?: number ) { - const contractPoolWithSigner = this.poolContract.connect(signer); - await this.validateLPBalance(toIndex); - return moveQuoteToken( - contractPoolWithSigner, + signer, + this.pool.poolAddress, maxAmountToMove, this.index, toIndex, diff --git a/src/classes/Config.ts b/src/classes/Config.ts index e177111..39c45e7 100644 --- a/src/classes/Config.ts +++ b/src/classes/Config.ts @@ -11,6 +11,7 @@ class Config { static ajnaToken: Address; static grantFund: Address; static burnWrapper: Address; + static lenderHelper: Address; /** * Allows consumer to configure with their own addresses. @@ -20,6 +21,7 @@ class Config { * @param ajnaToken address of the AJNA token contract * @param grantFund address of the ecosystem coordination contract * @param burnWrapper address of the contract used to wrap AJNA for transferring across an L2 bridge + * @param lenderHelper address of the contract used as a helper to add and move liquidity across buckets */ constructor( erc20PoolFactory: Address, @@ -28,7 +30,8 @@ class Config { positionManager: Address, ajnaToken: Address, grantFund: Address, - burnWrapper: Address + burnWrapper: Address, + lenderHelper: Address ) { Config.erc20PoolFactory = erc20PoolFactory; Config.erc721PoolFactory = erc721PoolFactory; @@ -37,6 +40,7 @@ class Config { Config.ajnaToken = ajnaToken; Config.grantFund = grantFund; Config.burnWrapper = burnWrapper; + Config.lenderHelper = lenderHelper; } /** @@ -50,7 +54,8 @@ class Config { process.env.AJNA_POSITION_MANAGER || '', process.env.AJNA_TOKEN_ADDRESS || '', process.env.AJNA_GRANT_FUND || '', - process.env.AJNA_BURN_WRAPPER || '' + process.env.AJNA_BURN_WRAPPER || '', + process.env.AJNA_LENDER_HELPER || '' ); } } diff --git a/src/classes/Pool.ts b/src/classes/Pool.ts index d804a5f..5ca0031 100644 --- a/src/classes/Pool.ts +++ b/src/classes/Pool.ts @@ -51,6 +51,7 @@ import { Liquidation } from './Liquidation'; import { getBlockTime } from '../utils/time'; import { Bucket } from './Bucket'; import { getSubsetHash } from '../contracts/erc721-pool-factory'; +import { getLenderHelperContract } from '../contracts/lender-helper'; export interface LoanEstimate extends Loan { /** hypothetical lowest utilized price (LUP) assuming additional debt was drawn */ @@ -145,6 +146,7 @@ export abstract class Pool { name: string; utils: PoolUtils; ethcallProvider: ProviderMulti; + lenderHelper: Contract; constructor( provider: SignerOrProvider, @@ -165,6 +167,7 @@ export abstract class Pool { this.contractMulti = contractMulti; this.quoteAddress = constants.AddressZero; this.collateralAddress = constants.AddressZero; + this.lenderHelper = getLenderHelperContract(this.provider); } async initialize() { @@ -839,6 +842,43 @@ export abstract class Pool { return approveLPTransferors(signer, this.contract, [addr]); } + /** + * Approve lend helper to manage quote token. + * @param signer pool user + * @param allowance normalized approval amount (or MaxUint256) + * @returns promise to transaction + */ + async quoteApproveHelper(signer: Signer, allowance: BigNumber) { + const denormalizedAllowance = allowance.eq(constants.MaxUint256) + ? allowance + : allowance.div(await quoteTokenScale(this.contract)); + + return approve(signer, this.lenderHelper.address, this.quoteAddress, denormalizedAllowance); + } + + async approveLenderHelperLPTransferor(signer: Signer) { + return approveLPTransferors(signer, this.contract, [this.lenderHelper.address]); + } + + async isLenderHelperLPTransferorApproved(signer: Signer): Promise { + const signerAddress = await signer.getAddress(); + + return await this.contract.approvedTransferors(signerAddress, this.lenderHelper.address); + } + + async increaseLenderHelperLPAllowance( + signer: Signer, + indexes: Array, + amounts: Array + ) { + if (indexes.length !== amounts.length) { + throw new SdkError('indexes and amounts must be same length'); + } + + const poolWithSigner = this.contract.connect(signer); + return increaseLPAllowance(poolWithSigner, this.lenderHelper.address, indexes, amounts); + } + async isLPTransferorApproved(signer: Signer): Promise { const transferor = getPositionManagerContract(signer).address; const signerAddress = await signer.getAddress(); diff --git a/src/constants/common.ts b/src/constants/common.ts index fdb8f87..dbc5f9f 100644 --- a/src/constants/common.ts +++ b/src/constants/common.ts @@ -30,6 +30,3 @@ export const FUNDING_STAGE = utils.keccak256(utils.toUtf8Bytes(DistributionPerio export const CHALLENGE_STAGE = utils.keccak256( utils.toUtf8Bytes(DistributionPeriodStage.CHALLENGE) ); - -// upper limit for bucket's LP balance -export const MIN_BUCKET_LP = BigNumber.from(1_000_000); diff --git a/src/contracts/lender-helper.ts b/src/contracts/lender-helper.ts new file mode 100644 index 0000000..7e0fec0 --- /dev/null +++ b/src/contracts/lender-helper.ts @@ -0,0 +1,55 @@ +import { BigNumber } from 'ethers'; +import { Config } from '../constants'; +import { + Address, + AjnaLenderHelper__factory, + Signer, + SignerOrProvider, + TransactionOverrides, +} from '../types'; +import { createTransaction } from '../utils'; + +export const getLenderHelperContract = (provider: SignerOrProvider) => { + return AjnaLenderHelper__factory.connect(Config.lenderHelper, provider); +}; + +export async function addQuoteToken( + signer: Signer, + poolAddress: Address, + maxAmount: BigNumber, + bucketIndex: number, + expiry: number, + overrides?: TransactionOverrides +) { + const lenderHelperContract = getLenderHelperContract(signer); + + return await createTransaction( + lenderHelperContract, + { + methodName: 'addQuoteToken', + args: [poolAddress, maxAmount, bucketIndex, expiry], + }, + overrides + ); +} + +export async function moveQuoteToken( + signer: Signer, + poolAddress: Address, + maxAmount: BigNumber, + fromBucketIndex: number, + toBucketIndex: number, + expiry: number, + overrides?: TransactionOverrides +) { + const lenderHelperContract = getLenderHelperContract(signer); + + return await createTransaction( + lenderHelperContract, + { + methodName: 'moveQuoteToken', + args: [poolAddress, maxAmount, fromBucketIndex, toBucketIndex, expiry], + }, + overrides + ); +} diff --git a/src/tests/erc20-pool-contracts.spec.ts b/src/tests/erc20-pool-contracts.spec.ts index b85371d..e322103 100644 --- a/src/tests/erc20-pool-contracts.spec.ts +++ b/src/tests/erc20-pool-contracts.spec.ts @@ -13,7 +13,6 @@ import { indexToPrice, priceToIndex } from '../utils/pricing'; import { getBlockTime, getExpiry } from '../utils/time'; import { TEST_CONFIG as config } from './test-constants'; import { submitAndVerifyTransaction } from './test-utils'; -import { SdkError } from '../types'; jest.setTimeout(80000); @@ -82,73 +81,21 @@ describe('ERC20 Pool', () => { const quoteAmount = toWad(10_000); const bucket = await pool.getBucketByIndex(2000); // price 46776.6533691354 - let tx = await pool.quoteApprove(signerLender, quoteAmount); + let tx = await pool.quoteApproveHelper(signerLender, quoteAmount); await submitAndVerifyTransaction(tx); - tx = await bucket.addQuoteToken(signerLender, quoteAmount); - await submitAndVerifyTransaction(tx); - const bucketStatus = await bucket.getStatus(); - expect(bucketStatus.bucketLP.gt(0)).toBe(true); - expect(bucketStatus.exchangeRate.eq(toWad(1))).toBe(true); - - const lpBalance = await bucket.lpBalance(signerLender.address); - const depositLessFee = '9999.54337899543379'; - expect(fromWad(lpBalance)).toEqual(depositLessFee); - }); - - it('should fail addQuoteToken with dust', async () => { - const quoteDustAmount = BigNumber.from(100); - const quoteAmount = toWad(10_000); - const bucket = await pool.getBucketByIndex(999); - - let tx = await pool.quoteApprove(signerLender, quoteDustAmount.add(quoteAmount)); + tx = await pool.approveLenderHelperLPTransferor(signerLender); await submitAndVerifyTransaction(tx); - tx = await bucket.addQuoteToken(signerLender, quoteDustAmount); + tx = await bucket.addQuoteToken(signerLender, quoteAmount); await submitAndVerifyTransaction(tx); const bucketStatus = await bucket.getStatus(); - - expect(async () => await bucket.addQuoteToken(signerLender, quoteAmount)).rejects.toThrow( - SdkError - ); - expect(bucketStatus.bucketLP.gt(0)).toBe(true); expect(bucketStatus.exchangeRate.eq(toWad(1))).toBe(true); const lpBalance = await bucket.lpBalance(signerLender.address); - const depositLessFee = '0.0000000000000001'; - expect(fromWad(lpBalance)).toEqual(depositLessFee); - }); - - it('should fail moveQuoteToken with dust', async () => { - const quoteDustAmount = BigNumber.from(100); - const quoteAmount = toWad(10_000); - const bucketIndexFrom = 997; - const bucketIndexTo = 998; - const bucketFrom = await pool.getBucketByIndex(bucketIndexFrom); - const bucketTo = await pool.getBucketByIndex(bucketIndexTo); - - let tx = await pool.quoteApprove(signerLender, quoteDustAmount.add(quoteAmount)); - await submitAndVerifyTransaction(tx); - - tx = await bucketTo.addQuoteToken(signerLender, quoteDustAmount); - await submitAndVerifyTransaction(tx); - - tx = await bucketFrom.addQuoteToken(signerLender, quoteAmount); - await submitAndVerifyTransaction(tx); - - expect( - async () => await bucketFrom.moveQuoteToken(signerLender, bucketIndexTo, quoteAmount) - ).rejects.toThrow(SdkError); - - const bucketToStatus = await bucketTo.getStatus(); - - expect(bucketToStatus.bucketLP.gt(0)).toBe(true); - expect(bucketToStatus.exchangeRate.eq(toWad(1))).toBe(true); - - const lpBalance = await bucketTo.lpBalance(signerLender.address); - const depositLessFee = '0.0000000000000001'; + const depositLessFee = '9999.54337899543379'; expect(fromWad(lpBalance)).toEqual(depositLessFee); }); @@ -301,7 +248,17 @@ describe('ERC20 Pool', () => { const fromLpBefore = await bucketFrom.lpBalance(signerLender.address); const toLpBefore = await bucketTo.lpBalance(signerLender.address); - const tx = await bucketFrom.moveQuoteToken(signerLender, bucketIndexTo, maxAmountToMove); + let tx = await pool.approveLenderHelperLPTransferor(signerLender); + await submitAndVerifyTransaction(tx); + + tx = await pool.increaseLenderHelperLPAllowance( + signerLender, + [bucketIndexFrom], + [maxAmountToMove] + ); + await submitAndVerifyTransaction(tx); + + tx = await bucketFrom.moveQuoteToken(signerLender, bucketIndexTo, maxAmountToMove); await submitAndVerifyTransaction(tx); const fromLpAfter = await bucketFrom.lpBalance(signerLender.address); @@ -678,6 +635,12 @@ describe('ERC20 Pool', () => { // ETH/DAI (collateral / quote) const bucket = await pool.getBucketByIndex(2632); + tx = await pool.quoteApproveHelper(signerLender, quoteAmount); + await submitAndVerifyTransaction(tx); + + tx = await pool.approveLenderHelperLPTransferor(signerLender); + await submitAndVerifyTransaction(tx); + tx = await pool.quoteApprove(signerLender, quoteAmount); await tx.verifyAndSubmit(); tx = await bucket.addQuoteToken(signerLender, quoteAmount); @@ -760,9 +723,12 @@ describe('ERC20 Pool', () => { tx = await poolA.addCollateral(signerLender, bucketIndex, collateralAmount); await tx.verifyAndSubmit(); - // lender2 deposits quote token - tx = await poolA.quoteApprove(signerLender2, quoteAmount); - await tx.verifyAndSubmit(); + tx = await poolA.quoteApproveHelper(signerLender2, quoteAmount); + await submitAndVerifyTransaction(tx); + + tx = await poolA.approveLenderHelperLPTransferor(signerLender2); + await submitAndVerifyTransaction(tx); + tx = await bucket.addQuoteToken(signerLender2, quoteAmount); await tx.verifyAndSubmit(); @@ -818,9 +784,12 @@ describe('ERC20 Pool', () => { tx = await poolA.addCollateral(signerLender, bucketIndex1, collateralAmount); await tx.verifyAndSubmit(); - // lender deposits quote token to bucket1 and bucket2 - tx = await poolA.quoteApprove(signerLender, quoteAmount.mul(2)); - await tx.verifyAndSubmit(); + tx = await poolA.quoteApproveHelper(signerLender, quoteAmount.mul(2)); + await submitAndVerifyTransaction(tx); + + tx = await poolA.approveLenderHelperLPTransferor(signerLender); + await submitAndVerifyTransaction(tx); + tx = await bucket1.addQuoteToken(signerLender, quoteAmount); await tx.verifyAndSubmit(); tx = await bucket2.addQuoteToken(signerLender, quoteAmount); diff --git a/src/tests/erc20-pool-liquidations.spec.ts b/src/tests/erc20-pool-liquidations.spec.ts index 887a4be..9d957e0 100644 --- a/src/tests/erc20-pool-liquidations.spec.ts +++ b/src/tests/erc20-pool-liquidations.spec.ts @@ -45,6 +45,12 @@ describe('ERC20 Liquidations', () => { let tx = await pool.quoteApprove(signerLender, toWad(40)); await submitAndVerifyTransaction(tx); + tx = await pool.quoteApproveHelper(signerLender, toWad(40)); + await submitAndVerifyTransaction(tx); + + tx = await pool.approveLenderHelperLPTransferor(signerLender); + await submitAndVerifyTransaction(tx); + // add 9 quote tokens to 2001 bucket const higherBucket = await pool.getBucketByIndex(2001); let quoteAmount = toWad(9); @@ -235,9 +241,12 @@ describe('ERC20 Liquidations', () => { const allowance = 100000000; const quoteAmount = 10; - // lender adds liquidity - let tx = await pool.quoteApprove(signerLender, toWad(allowance)); + let tx = await pool.quoteApproveHelper(signerLender, toWad(allowance)); await submitAndVerifyTransaction(tx); + + tx = await pool.approveLenderHelperLPTransferor(signerLender); + await submitAndVerifyTransaction(tx); + tx = await bucket.addQuoteToken(signerLender, toWad(quoteAmount)); await submitAndVerifyTransaction(tx); diff --git a/src/tests/erc721-pool-contracts.spec.ts b/src/tests/erc721-pool-contracts.spec.ts index b1c8b61..e3b4842 100644 --- a/src/tests/erc721-pool-contracts.spec.ts +++ b/src/tests/erc721-pool-contracts.spec.ts @@ -203,8 +203,12 @@ describe('ERC721 Pool', () => { it('liquidity may be added to and removed from a NFT pool', async () => { // add liquidity const quoteAmount = toWad(100); - let tx = await poolDuckDaiSubset.quoteApprove(signerLender, quoteAmount); + let tx = await poolDuckDaiSubset.quoteApproveHelper(signerLender, quoteAmount); await submitAndVerifyTransaction(tx); + + tx = await poolDuckDaiSubset.approveLenderHelperLPTransferor(signerLender); + await submitAndVerifyTransaction(tx); + const bucket = await poolDuckDaiSubset.getBucketByPrice(toWad(200)); tx = await bucket.addQuoteToken(signerLender, quoteAmount); await submitAndVerifyTransaction(tx); diff --git a/src/tests/erc721-pool-liquidations.spec.ts b/src/tests/erc721-pool-liquidations.spec.ts index f6620cb..6753cdb 100644 --- a/src/tests/erc721-pool-liquidations.spec.ts +++ b/src/tests/erc721-pool-liquidations.spec.ts @@ -67,15 +67,23 @@ describe('ERC721 Liquidations', () => { let tx = await poolDuckDai.quoteApprove(signerLender, toWad(60000)); await submitAndVerifyTransaction(tx); + tx = await poolDuckDai.quoteApproveHelper(signerLender, toWad(60000)); + await submitAndVerifyTransaction(tx); + + tx = await poolDuckDai.approveLenderHelperLPTransferor(signerLender); + await submitAndVerifyTransaction(tx); + // add 21,000 quote tokens to 2001 bucket const higherBucket = await poolDuckDai.getBucketByIndex(2001); let quoteAmount = toWad(21000); + tx = await higherBucket.addQuoteToken(signerLender, quoteAmount); await submitAndVerifyTransaction(tx); // add 5000 quote tokens to 2500 bucket const lowerBucket = await poolDuckDai.getBucketByIndex(2500); quoteAmount = toWad(5000); + tx = await lowerBucket.addQuoteToken(signerLender, quoteAmount); await submitAndVerifyTransaction(tx); @@ -254,8 +262,12 @@ describe('ERC721 Liquidations', () => { expect(auctionStatus.debtToCover.gt(toWad('3.5'))).toBeTruthy(); // lender adds liquidity - tx = await poolDuckDai.quoteApprove(signerLender, toWad(allowance)); + tx = await poolDuckDai.quoteApproveHelper(signerLender, toWad(allowance)); await submitAndVerifyTransaction(tx); + + tx = await poolDuckDai.approveLenderHelperLPTransferor(signerLender); + await submitAndVerifyTransaction(tx); + tx = await bucket.addQuoteToken(signerLender, toWad(quoteAmount)); await submitAndVerifyTransaction(tx); @@ -298,8 +310,12 @@ describe('ERC721 Liquidations', () => { expect(auctionStatus.debtToCover.gt(toWad('3.5'))).toBeTruthy(); // lender adds liquidity - tx = await poolDuckDai.quoteApprove(signerLender, toWad(allowance)); + tx = await poolDuckDai.quoteApproveHelper(signerLender, toWad(allowance)); await submitAndVerifyTransaction(tx); + + tx = await poolDuckDai.approveLenderHelperLPTransferor(signerLender); + await submitAndVerifyTransaction(tx); + tx = await bucket.addQuoteToken(signerLender, toWad(quoteAmount)); await submitAndVerifyTransaction(tx); diff --git a/src/tests/position-manager.spec.ts b/src/tests/position-manager.spec.ts index af51813..f67f738 100644 --- a/src/tests/position-manager.spec.ts +++ b/src/tests/position-manager.spec.ts @@ -20,15 +20,19 @@ async function addQuoteTokensByIndexes( indexes: Array, amounts: Array ) { - const totalAmounts = amounts.reduce((a, b) => a.add(b)); - const res = await pool.quoteApprove(signer, totalAmounts); - await submitAndVerifyTransaction(res); + const totalAmount = amounts.reduce((a, b) => a.add(b)); + + let tx = await pool.quoteApproveHelper(signer, totalAmount); + await submitAndVerifyTransaction(tx); + + tx = await pool.approveLenderHelperLPTransferor(signer); + await submitAndVerifyTransaction(tx); let i = 0; for (const index of indexes) { const bucket = await pool.getBucketByIndex(index); - const res = await bucket.addQuoteToken(signer, amounts[i]); - await submitAndVerifyTransaction(res); + const tx = await bucket.addQuoteToken(signer, amounts[i]); + await submitAndVerifyTransaction(tx); i++; } } diff --git a/src/tests/test-constants.ts b/src/tests/test-constants.ts index 4fe2d40..8db31ab 100644 --- a/src/tests/test-constants.ts +++ b/src/tests/test-constants.ts @@ -14,6 +14,7 @@ const positionManagerAddress = '0xdF7403003a16c49ebA5883bB5890d474794cea5a'; const ajnaTokenAddress = '0x25Af17eF4E2E6A4A2CE586C9D25dF87FD84D4a7d'; const burnWrapperAddress = '0xE340B87CEd1af1AbE1CE8D617c84B7f168e3b18b'; const grantFundAddress = '0x0b3A0ea1Fc7207d3e3ed9973025dA9d0e8fb0F3f'; +const lenderHelper = '0x73c8605EDE83C7CfB148e7190375350019043Ff7'; export const testnetAddresses = new Config( erc20PoolFactoryAddress, @@ -22,5 +23,6 @@ export const testnetAddresses = new Config( positionManagerAddress, ajnaTokenAddress, grantFundAddress, - burnWrapperAddress + burnWrapperAddress, + lenderHelper ); diff --git a/src/types/contracts/AjnaLenderHelper.ts b/src/types/contracts/AjnaLenderHelper.ts new file mode 100644 index 0000000..40284bd --- /dev/null +++ b/src/types/contracts/AjnaLenderHelper.ts @@ -0,0 +1,169 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +import type { + BaseContract, + BigNumber, + BigNumberish, + BytesLike, + CallOverrides, + ContractTransaction, + Overrides, + PopulatedTransaction, + Signer, + utils, +} from 'ethers'; +import type { FunctionFragment, Result } from '@ethersproject/abi'; +import type { Listener, Provider } from '@ethersproject/providers'; +import type { TypedEventFilter, TypedEvent, TypedListener, OnEvent } from './common'; + +export interface AjnaLenderHelperInterface extends utils.Interface { + functions: { + 'addQuoteToken(address,uint256,uint256,uint256)': FunctionFragment; + 'moveQuoteToken(address,uint256,uint256,uint256,uint256)': FunctionFragment; + }; + + getFunction(nameOrSignatureOrTopic: 'addQuoteToken' | 'moveQuoteToken'): FunctionFragment; + + encodeFunctionData( + functionFragment: 'addQuoteToken', + values: [string, BigNumberish, BigNumberish, BigNumberish] + ): string; + encodeFunctionData( + functionFragment: 'moveQuoteToken', + values: [string, BigNumberish, BigNumberish, BigNumberish, BigNumberish] + ): string; + + decodeFunctionResult(functionFragment: 'addQuoteToken', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'moveQuoteToken', data: BytesLike): Result; + + events: {}; +} + +export interface AjnaLenderHelper extends BaseContract { + contractName: 'AjnaLenderHelper'; + + connect(signerOrProvider: Signer | Provider | string): this; + attach(addressOrName: string): this; + deployed(): Promise; + + interface: AjnaLenderHelperInterface; + + queryFilter( + event: TypedEventFilter, + fromBlockOrBlockhash?: string | number | undefined, + toBlock?: string | number | undefined + ): Promise>; + + listeners( + eventFilter?: TypedEventFilter + ): Array>; + listeners(eventName?: string): Array; + removeAllListeners(eventFilter: TypedEventFilter): this; + removeAllListeners(eventName?: string): this; + off: OnEvent; + on: OnEvent; + once: OnEvent; + removeListener: OnEvent; + + functions: { + addQuoteToken( + pool_: string, + maxAmount_: BigNumberish, + index_: BigNumberish, + expiry_: BigNumberish, + overrides?: Overrides & { from?: string } + ): Promise; + + moveQuoteToken( + pool_: string, + maxAmount_: BigNumberish, + fromIndex_: BigNumberish, + toIndex_: BigNumberish, + expiry_: BigNumberish, + overrides?: Overrides & { from?: string } + ): Promise; + }; + + addQuoteToken( + pool_: string, + maxAmount_: BigNumberish, + index_: BigNumberish, + expiry_: BigNumberish, + overrides?: Overrides & { from?: string } + ): Promise; + + moveQuoteToken( + pool_: string, + maxAmount_: BigNumberish, + fromIndex_: BigNumberish, + toIndex_: BigNumberish, + expiry_: BigNumberish, + overrides?: Overrides & { from?: string } + ): Promise; + + callStatic: { + addQuoteToken( + pool_: string, + maxAmount_: BigNumberish, + index_: BigNumberish, + expiry_: BigNumberish, + overrides?: CallOverrides + ): Promise<[BigNumber, BigNumber] & { bucketLP_: BigNumber; addedAmount_: BigNumber }>; + + moveQuoteToken( + pool_: string, + maxAmount_: BigNumberish, + fromIndex_: BigNumberish, + toIndex_: BigNumberish, + expiry_: BigNumberish, + overrides?: CallOverrides + ): Promise< + [BigNumber, BigNumber, BigNumber] & { + fromBucketRedeemedLP_: BigNumber; + toBucketAwardedLP_: BigNumber; + movedAmount_: BigNumber; + } + >; + }; + + filters: {}; + + estimateGas: { + addQuoteToken( + pool_: string, + maxAmount_: BigNumberish, + index_: BigNumberish, + expiry_: BigNumberish, + overrides?: Overrides & { from?: string } + ): Promise; + + moveQuoteToken( + pool_: string, + maxAmount_: BigNumberish, + fromIndex_: BigNumberish, + toIndex_: BigNumberish, + expiry_: BigNumberish, + overrides?: Overrides & { from?: string } + ): Promise; + }; + + populateTransaction: { + addQuoteToken( + pool_: string, + maxAmount_: BigNumberish, + index_: BigNumberish, + expiry_: BigNumberish, + overrides?: Overrides & { from?: string } + ): Promise; + + moveQuoteToken( + pool_: string, + maxAmount_: BigNumberish, + fromIndex_: BigNumberish, + toIndex_: BigNumberish, + expiry_: BigNumberish, + overrides?: Overrides & { from?: string } + ): Promise; + }; +} diff --git a/src/types/contracts/factories/AjnaLenderHelper__factory.ts b/src/types/contracts/factories/AjnaLenderHelper__factory.ts new file mode 100644 index 0000000..e3ae050 --- /dev/null +++ b/src/types/contracts/factories/AjnaLenderHelper__factory.ts @@ -0,0 +1,194 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ + +import { Contract, Signer, utils } from 'ethers'; +import type { Provider } from '@ethersproject/providers'; +import type { AjnaLenderHelper, AjnaLenderHelperInterface } from '../AjnaLenderHelper'; + +const _abi = [ + { + inputs: [], + name: 'BucketIndexOutOfBounds', + type: 'error', + }, + { + inputs: [], + name: 'InsufficientLP', + type: 'error', + }, + { + inputs: [ + { + internalType: 'int256', + name: 'x', + type: 'int256', + }, + ], + name: 'PRBMathSD59x18__Exp2InputTooBig', + type: 'error', + }, + { + inputs: [ + { + internalType: 'int256', + name: 'x', + type: 'int256', + }, + ], + name: 'PRBMathSD59x18__FromIntOverflow', + type: 'error', + }, + { + inputs: [ + { + internalType: 'int256', + name: 'x', + type: 'int256', + }, + ], + name: 'PRBMathSD59x18__FromIntUnderflow', + type: 'error', + }, + { + inputs: [ + { + internalType: 'int256', + name: 'x', + type: 'int256', + }, + ], + name: 'PRBMathSD59x18__LogInputTooSmall', + type: 'error', + }, + { + inputs: [], + name: 'PRBMathSD59x18__MulInputTooSmall', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'rAbs', + type: 'uint256', + }, + ], + name: 'PRBMathSD59x18__MulOverflow', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'prod1', + type: 'uint256', + }, + ], + name: 'PRBMath__MulDivFixedPointOverflow', + type: 'error', + }, + { + inputs: [], + name: 'RoundedAmountExceededRequestedMaximum', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'pool_', + type: 'address', + }, + { + internalType: 'uint256', + name: 'maxAmount_', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'index_', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'expiry_', + type: 'uint256', + }, + ], + name: 'addQuoteToken', + outputs: [ + { + internalType: 'uint256', + name: 'bucketLP_', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'addedAmount_', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'pool_', + type: 'address', + }, + { + internalType: 'uint256', + name: 'maxAmount_', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'fromIndex_', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'toIndex_', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'expiry_', + type: 'uint256', + }, + ], + name: 'moveQuoteToken', + outputs: [ + { + internalType: 'uint256', + name: 'fromBucketRedeemedLP_', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'toBucketAwardedLP_', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'movedAmount_', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, +] as const; + +export class AjnaLenderHelper__factory { + static readonly abi = _abi; + static createInterface(): AjnaLenderHelperInterface { + return new utils.Interface(_abi) as AjnaLenderHelperInterface; + } + static connect(address: string, signerOrProvider: Signer | Provider): AjnaLenderHelper { + return new Contract(address, _abi, signerOrProvider) as AjnaLenderHelper; + } +} diff --git a/src/types/contracts/factories/index.ts b/src/types/contracts/factories/index.ts index ae94ee8..c2251b6 100644 --- a/src/types/contracts/factories/index.ts +++ b/src/types/contracts/factories/index.ts @@ -1,6 +1,7 @@ /* Autogenerated file. Do not edit manually. */ /* tslint:disable */ /* eslint-disable */ +export { AjnaLenderHelper__factory } from './AjnaLenderHelper__factory'; export { AjnaToken__factory } from './AjnaToken__factory'; export { BurnWrappedAjna__factory } from './BurnWrappedAjna__factory'; export { ERC20__factory } from './ERC20__factory'; diff --git a/src/types/contracts/index.ts b/src/types/contracts/index.ts index a4ae2b7..24a9f87 100644 --- a/src/types/contracts/index.ts +++ b/src/types/contracts/index.ts @@ -1,6 +1,7 @@ /* Autogenerated file. Do not edit manually. */ /* tslint:disable */ /* eslint-disable */ +export type { AjnaLenderHelper } from './AjnaLenderHelper'; export type { AjnaToken } from './AjnaToken'; export type { BurnWrappedAjna } from './BurnWrappedAjna'; export type { ERC20 } from './ERC20'; @@ -13,6 +14,7 @@ export type { GrantFund } from './GrantFund'; export type { PoolInfoUtils } from './PoolInfoUtils'; export type { PositionManager } from './PositionManager'; export * as factories from './factories'; +export { AjnaLenderHelper__factory } from './factories/AjnaLenderHelper__factory'; export { AjnaToken__factory } from './factories/AjnaToken__factory'; export { BurnWrappedAjna__factory } from './factories/BurnWrappedAjna__factory'; export { ERC20__factory } from './factories/ERC20__factory';