From f2f8f55b7b673a4e2163e8a317d7fcca4d279e7d Mon Sep 17 00:00:00 2001 From: Kaspar Kallas Date: Fri, 13 Dec 2024 16:57:46 +0700 Subject: [PATCH] forward ETH value --- packages/sdk-core/src/BatchCall.ts | 26 ++++- packages/sdk-core/src/Operation.ts | 4 + packages/sdk-core/test/2_operation.test.ts | 76 +++++-------- packages/sdk-core/test/3_batch_call.test.ts | 115 +++++++++++++++++++- 4 files changed, 168 insertions(+), 53 deletions(-) diff --git a/packages/sdk-core/src/BatchCall.ts b/packages/sdk-core/src/BatchCall.ts index db2f965b7a..ac52a87cae 100644 --- a/packages/sdk-core/src/BatchCall.ts +++ b/packages/sdk-core/src/BatchCall.ts @@ -1,5 +1,5 @@ import { JsonFragment } from "@ethersproject/abi"; -import { ethers } from "ethers"; +import { BigNumber, ethers } from "ethers"; import Host from "./Host"; import Operation, { BatchOperationType } from "./Operation"; @@ -15,6 +15,7 @@ export interface OperationStruct { readonly operationType: number; readonly target: string; readonly data: string; + readonly value?: ethers.BigNumber; } export const batchOperationTypeStringToTypeMap = new Map< @@ -91,10 +92,25 @@ export default class BatchCall { const operationStructArray = await Promise.all( this.getOperationStructArrayPromises ); - const tx = - this.host.contract.populateTransaction.batchCall( - operationStructArray - ); + + const values = operationStructArray + .filter((x) => x.value?.gt(BigNumber.from(0))) + .map((x) => x.value); + + if (values.length > 1) { + throw new SFError({ + type: "BATCH_CALL_ERROR", + message: + "There are multiple values in the batch call. The value can only be forwarded to one receiving operation.", + }); + } + + const tx = this.host.contract.populateTransaction.batchCall( + operationStructArray, + { + value: values?.length > 0 ? values[0] : undefined, + } + ); return new Operation(tx, "UNSUPPORTED"); } diff --git a/packages/sdk-core/src/Operation.ts b/packages/sdk-core/src/Operation.ts index 066ac582f4..87302d4d87 100644 --- a/packages/sdk-core/src/Operation.ts +++ b/packages/sdk-core/src/Operation.ts @@ -166,6 +166,7 @@ export default class Operation { operationType: batchOperationType, target: functionArgs["agreementClass"], data, + value: populatedTransaction.value, }; } @@ -180,6 +181,7 @@ export default class Operation { operationType: batchOperationType, target: functionArgs["app"], data: functionArgs["callData"], + value: populatedTransaction.value, }; } @@ -191,6 +193,7 @@ export default class Operation { operationType: batchOperationType, target: populatedTransaction.to, data: populatedTransaction.data, + value: populatedTransaction.value, }; } @@ -199,6 +202,7 @@ export default class Operation { operationType: batchOperationType, target: populatedTransaction.to, data: removeSigHashFromCallData(populatedTransaction.data), + value: populatedTransaction.value, }; }; } diff --git a/packages/sdk-core/test/2_operation.test.ts b/packages/sdk-core/test/2_operation.test.ts index 18b47e3aba..e3d383987b 100644 --- a/packages/sdk-core/test/2_operation.test.ts +++ b/packages/sdk-core/test/2_operation.test.ts @@ -2,14 +2,14 @@ import { expect } from "chai"; import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; import { Framework } from "../src/index"; import { getPerSecondFlowRateByMonth } from "../src"; -import { ERC20, ERC20__factory, IConstantFlowAgreementV1__factory, TestToken, TestToken__factory } from "../src/typechain-types"; +import { IConstantFlowAgreementV1__factory } from "../src/typechain-types"; import Operation from "../src/Operation"; import hre from "hardhat"; import { SuperAppTester } from "../typechain-types"; import { SuperAppTester__factory } from "../typechain-types"; const cfaInterface = IConstantFlowAgreementV1__factory.createInterface(); import { TestEnvironment, makeSuite } from "./TestEnvironment"; -import { BigNumber, Contract, ethers } from "ethers"; +import { BigNumber, ethers } from "ethers"; import multiplyGasLimit from "../src/multiplyGasLimit"; /** @@ -174,6 +174,33 @@ makeSuite("Operation Tests", (testEnv: TestEnvironment) => { expect(txn.gasLimit).to.equal("500000"); }); + it("Should move ETH value passed from the Overrides", async () => { + const value = BigNumber.from(Number(0.1e18).toString()); + + const beforeBalanceAlice = await testEnv.alice.getBalance(); + const beforeBalanceBob = await testEnv.bob.getBalance(); + + expect(beforeBalanceAlice.gt(value)).to.be.true; + + const operation = testEnv.sdkFramework.operation( + testEnv.alice.populateTransaction({ + to: testEnv.bob.address, + value + }) as Promise, + "SIMPLE_FORWARD_CALL" + ); + + const txn = await operation.exec(testEnv.alice, 2); + const txReceipt = await txn.wait(); + expect(txReceipt.status).to.equal(1); + + const afterBalanceAlice = await testEnv.alice.getBalance(); + const afterBalanceBob = await testEnv.bob.getBalance(); + + expect(afterBalanceBob.sub(beforeBalanceBob)).to.equal(value); + expect(beforeBalanceAlice.sub(afterBalanceAlice).gte(value)).to.be.true; + }); + it("Should throw an error when trying to execute a transaction with faulty callData", async () => { const callData = cfaInterface.encodeFunctionData("createFlow", [ testEnv.wrapperSuperToken.address, @@ -209,51 +236,6 @@ makeSuite("Operation Tests", (testEnv: TestEnvironment) => { } }); - it("Should be able to execute SimpleForwarder operation from framework.", async () => { - const contract = (new Contract( - testEnv.wrapperSuperToken.underlyingToken.address, - TestToken__factory.abi - ) as TestToken).connect(testEnv.alice); - - const beforeBalance = await contract.balanceOf(testEnv.alice.address); - - const txn1 = contract - .populateTransaction.mint( - testEnv.alice.address, - "100" - ); - const txn2 = contract - .populateTransaction.mint( - testEnv.alice.address, - "200" - ); - - const operation1 = testEnv.sdkFramework.operation( - txn1, - "SIMPLE_FORWARD_CALL" - ); - const operation2 = testEnv.sdkFramework.operation( - txn2, - "SIMPLE_FORWARD_CALL" - ); - - const batchCall = testEnv.sdkFramework.batchCall( - [ - operation1, - operation2 - ] - ); - - const txResponse = await batchCall.exec(testEnv.alice); - const txReceipt = await txResponse.wait(); - expect(txReceipt.status).to.equal(1); - - const afterBalance = await contract.balanceOf(testEnv.alice.address); - - expect(beforeBalance.lt(afterBalance)).to.be.true; - expect(afterBalance.sub(BigNumber.from(300)).eq(beforeBalance)).to.be.true; - }); - context( "Should be able to get and execute a signed transaction", async () => { diff --git a/packages/sdk-core/test/3_batch_call.test.ts b/packages/sdk-core/test/3_batch_call.test.ts index 740a063061..e62c46a3e1 100644 --- a/packages/sdk-core/test/3_batch_call.test.ts +++ b/packages/sdk-core/test/3_batch_call.test.ts @@ -2,10 +2,12 @@ import { expect } from "chai"; import { AUTHORIZE_FULL_CONTROL, Operation, + TestToken, + TestToken__factory, getPerSecondFlowRateByMonth, toBN, } from "../src"; -import { ethers } from "ethers"; +import { BigNumber, Contract, ethers } from "ethers"; import { createCallAppActionOperation } from "./2_operation.test"; import { TestEnvironment, makeSuite } from "./TestEnvironment"; @@ -408,4 +410,115 @@ makeSuite("Batch Call Tests", (testEnv: TestEnvironment) => { "0" ); }); + + + it("Should be able to execute SimpleForwarder batch call", async () => { + const contract = (new Contract( + testEnv.wrapperSuperToken.underlyingToken.address, + TestToken__factory.abi + ) as TestToken).connect(testEnv.alice); + + const beforeBalance = await contract.balanceOf(testEnv.alice.address); + + const txn1 = contract + .populateTransaction.mint( + testEnv.alice.address, + "100" + ); + const txn2 = contract + .populateTransaction.mint( + testEnv.alice.address, + "200" + ); + + const operation1 = testEnv.sdkFramework.operation( + txn1, + "SIMPLE_FORWARD_CALL" + ); + const operation2 = testEnv.sdkFramework.operation( + txn2, + "SIMPLE_FORWARD_CALL" + ); + + const batchCall = testEnv.sdkFramework.batchCall( + [ + operation1, + operation2 + ] + ); + + const txResponse = await batchCall.exec(testEnv.alice); + const txReceipt = await txResponse.wait(); + expect(txReceipt.status).to.equal(1); + + const afterBalance = await contract.balanceOf(testEnv.alice.address); + + expect(beforeBalance.lt(afterBalance)).to.be.true; + expect(afterBalance.sub(BigNumber.from(300)).eq(beforeBalance)).to.be.true; + }); + + it("Should be able to move ETH value", async () => { + const value = BigNumber.from(Number(0.1e18).toString()); + + const beforeBalanceAlice = await testEnv.alice.getBalance(); + const beforeBalanceBob = await testEnv.bob.getBalance(); + + expect(beforeBalanceAlice.gt(value)).to.be.true; + + const operation = testEnv.sdkFramework.operation( + testEnv.alice.populateTransaction({ + to: testEnv.bob.address, + value, + data: "0x" + }) as Promise, + "SIMPLE_FORWARD_CALL" + ); + + const batchCall = testEnv.sdkFramework.batchCall( + [ + operation + ] + ); + + const txn = await batchCall.exec(testEnv.alice, 2); + + const txReceipt = await txn.wait(); + expect(txReceipt.status).to.equal(1); + + const afterBalanceAlice = await testEnv.alice.getBalance(); + const afterBalanceBob = await testEnv.bob.getBalance(); + + expect(afterBalanceBob.sub(beforeBalanceBob)).to.equal(value); + expect(beforeBalanceAlice.sub(afterBalanceAlice).gte(value)).to.be.true; + }); + + it.only("Should throw an error when multiple Operations with ETH value", async () => { + const operation1 = testEnv.sdkFramework.operation( + testEnv.alice.populateTransaction({ + to: testEnv.bob.address, + value: BigNumber.from(Number(0.1e18).toString()), + data: "0x" + }) as Promise, + "SIMPLE_FORWARD_CALL" + ); + + const operation2 = testEnv.sdkFramework.operation( + testEnv.alice.populateTransaction({ + to: testEnv.bob.address, + value: BigNumber.from(Number(0.2e18).toString()), + data: "0x" + }) as Promise, + "SIMPLE_FORWARD_CALL" + ); + + const batchCall = testEnv.sdkFramework.batchCall( + [ + operation1, + operation2 + ] + ); + + await expect(batchCall.exec(testEnv.alice, 2)) + .to.be.rejectedWith("multiple values in the batch call"); + }); });