Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[sdk-core] new Operation types and ETH value forwarding #2042

Merged
merged 12 commits into from
Dec 16, 2024
1 change: 1 addition & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
# test utilities
lcov
actionlint
git
];

# solidity dev inputs
Expand Down
6 changes: 6 additions & 0 deletions packages/sdk-core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
### Changed
### Fixed

## [0.9.0] - 2024-12-13

### Added
- Handle new Operation types with BatchCall
- Forward ETH value with BatchCall and Operation

## [0.8.0] - 2024-08-01

### Breaking
Expand Down
4 changes: 2 additions & 2 deletions packages/sdk-core/package.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
{
"name": "@superfluid-finance/sdk-core",
"description": "SDK Core for building with Superfluid Protocol",
"version": "0.8.0",
"version": "0.9.0",
"bugs": "https://github.com/superfluid-finance/protocol-monorepo/issues",
"dependencies": {
"@superfluid-finance/ethereum-contracts": "1.12.0",
"@superfluid-finance/metadata": "^1.5.2",
"browserify": "17.0.0",
"graphql-request": "6.1.0",
"lodash": "4.17.21",
"tsify": "5.0.4"
Expand All @@ -16,6 +15,7 @@
"@graphql-codegen/near-operation-file-preset": "^3.0.0",
"@graphql-typed-document-node/core": "^3.2.0",
"ajv": "^8.17.1",
"browserify": "^17.0.1",
"ethers": "^5.7.2",
"get-graphql-schema": "^2.1.2",
"mocha": "^10.7.3"
Expand Down
28 changes: 23 additions & 5 deletions packages/sdk-core/src/BatchCall.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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<
Expand All @@ -30,6 +31,8 @@ export const batchOperationTypeStringToTypeMap = new Map<
["SUPERTOKEN_DOWNGRADE", 102],
["SUPERFLUID_CALL_AGREEMENT", 201],
["CALL_APP_ACTION", 202],
["SIMPLE_FORWARD_CALL", 301],
["ERC2771_FORWARD_CALL", 302],
]);

/**
Expand Down Expand Up @@ -89,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);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A possible situation to protect against would be that the operation with the ETH value would be the first payable call in the set of operations.

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");
}

Expand Down
21 changes: 19 additions & 2 deletions packages/sdk-core/src/Operation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ export type BatchOperationType =
| "SUPERTOKEN_UPGRADE" // 101
| "SUPERTOKEN_DOWNGRADE" // 102
| "SUPERFLUID_CALL_AGREEMENT" // 201
| "CALL_APP_ACTION"; // 202
| "CALL_APP_ACTION" // 202
| "SIMPLE_FORWARD_CALL" // 301
| "ERC2771_FORWARD_CALL"; // 302

/**
* Operation Helper Class
Expand Down Expand Up @@ -164,6 +166,7 @@ export default class Operation {
operationType: batchOperationType,
target: functionArgs["agreementClass"],
data,
value: populatedTransaction.value,
};
}

Expand All @@ -178,14 +181,28 @@ export default class Operation {
operationType: batchOperationType,
target: functionArgs["app"],
data: functionArgs["callData"],
value: populatedTransaction.value,
};
}

// Handles remaining ERC20/ERC777/SuperToken Operations
if (
this.type === "SIMPLE_FORWARD_CALL" ||
this.type === "ERC2771_FORWARD_CALL"
) {
return {
operationType: batchOperationType,
target: populatedTransaction.to,
data: populatedTransaction.data,
value: populatedTransaction.value,
};
}

// Handles remaining ERC20/ERC777/SuperToken Operations (including SIMPLE_FORWARD_CALL and ERC2771_FORWARD_CALL)
return {
operationType: batchOperationType,
target: populatedTransaction.to,
data: removeSigHashFromCallData(populatedTransaction.data),
value: populatedTransaction.value,
};
};
}
29 changes: 28 additions & 1 deletion packages/sdk-core/test/2_operation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { SuperAppTester } from "../typechain-types";
import { SuperAppTester__factory } from "../typechain-types";
const cfaInterface = IConstantFlowAgreementV1__factory.createInterface();
import { TestEnvironment, makeSuite } from "./TestEnvironment";
import { ethers } from "ethers";
import { BigNumber, ethers } from "ethers";
import multiplyGasLimit from "../src/multiplyGasLimit";

/**
Expand Down Expand Up @@ -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<ethers.PopulatedTransaction>,
"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,
Expand Down
115 changes: 114 additions & 1 deletion packages/sdk-core/test/3_batch_call.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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<ethers.PopulatedTransaction>,
"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("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<ethers.PopulatedTransaction>,
"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<ethers.PopulatedTransaction>,
"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");
});
});
2 changes: 1 addition & 1 deletion packages/subgraph/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"dependencies": {
"@graphprotocol/graph-cli": "0.80.1",
"@graphprotocol/graph-ts": "0.35.1",
"@superfluid-finance/sdk-core": "0.8.0",
"@superfluid-finance/sdk-core": "0.9.0",
"mustache": "4.2.0"
},
"devDependencies": {
Expand Down
12 changes: 6 additions & 6 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6446,10 +6446,10 @@ browserify-zlib@~0.2.0:
dependencies:
pako "~1.0.5"

browserify@17.0.0:
version "17.0.0"
resolved "https://registry.yarnpkg.com/browserify/-/browserify-17.0.0.tgz#4c48fed6c02bfa2b51fd3b670fddb805723cdc22"
integrity sha512-SaHqzhku9v/j6XsQMRxPyBrSP3gnwmE27gLJYZgMT2GeK3J0+0toN+MnuNYDfHwVGQfLiMZ7KSNSIXHemy905w==
browserify@^17.0.1:
version "17.0.1"
resolved "https://registry.yarnpkg.com/browserify/-/browserify-17.0.1.tgz#d822fa701431ca94cb405b033bb2551951e5d97d"
integrity sha512-pxhT00W3ylMhCHwG5yfqtZjNnFuX5h2IJdaBfSo4ChaaBsIp9VLrEMQ1bHV+Xr1uLPXuNDDM1GlJkjli0qkRsw==
dependencies:
JSONStream "^1.0.3"
assert "^1.4.0"
Expand All @@ -6468,7 +6468,7 @@ browserify@17.0.0:
duplexer2 "~0.1.2"
events "^3.0.0"
glob "^7.1.0"
has "^1.0.0"
hasown "^2.0.0"
htmlescape "^1.1.0"
https-browserify "^1.0.0"
inherits "~2.0.1"
Expand Down Expand Up @@ -11083,7 +11083,7 @@ has-unicode@2.0.1:
resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==

has@^1.0.0, has@^1.0.3:
has@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
Expand Down
Loading