Skip to content

Commit 49805bf

Browse files
authored
[sdk-core] new Operation types and ETH value forwarding (#2042)
* add operation codes * convert to structs * simplify operation handling * simplify operation handling * simplify operation handling * make test pass * add git to nix flakes * forward ETH value * remove .only * bump versions * update yarn.lock * move browserify into devDependencies
1 parent 85696a9 commit 49805bf

File tree

9 files changed

+200
-18
lines changed

9 files changed

+200
-18
lines changed

flake.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
# test utilities
5656
lcov
5757
actionlint
58+
git
5859
];
5960

6061
# solidity dev inputs

packages/sdk-core/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
1010
### Changed
1111
### Fixed
1212

13+
## [0.9.0] - 2024-12-13
14+
15+
### Added
16+
- Handle new Operation types with BatchCall
17+
- Forward ETH value with BatchCall and Operation
18+
1319
## [0.8.0] - 2024-08-01
1420

1521
### Breaking

packages/sdk-core/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
{
22
"name": "@superfluid-finance/sdk-core",
33
"description": "SDK Core for building with Superfluid Protocol",
4-
"version": "0.8.0",
4+
"version": "0.9.0",
55
"bugs": "https://github.com/superfluid-finance/protocol-monorepo/issues",
66
"dependencies": {
77
"@superfluid-finance/ethereum-contracts": "1.12.0",
88
"@superfluid-finance/metadata": "^1.5.2",
9-
"browserify": "17.0.0",
109
"graphql-request": "6.1.0",
1110
"lodash": "4.17.21",
1211
"tsify": "5.0.4"
@@ -16,6 +15,7 @@
1615
"@graphql-codegen/near-operation-file-preset": "^3.0.0",
1716
"@graphql-typed-document-node/core": "^3.2.0",
1817
"ajv": "^8.17.1",
18+
"browserify": "^17.0.1",
1919
"ethers": "^5.7.2",
2020
"get-graphql-schema": "^2.1.2",
2121
"mocha": "^10.7.3"

packages/sdk-core/src/BatchCall.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { JsonFragment } from "@ethersproject/abi";
2-
import { ethers } from "ethers";
2+
import { BigNumber, ethers } from "ethers";
33

44
import Host from "./Host";
55
import Operation, { BatchOperationType } from "./Operation";
@@ -15,6 +15,7 @@ export interface OperationStruct {
1515
readonly operationType: number;
1616
readonly target: string;
1717
readonly data: string;
18+
readonly value?: ethers.BigNumber;
1819
}
1920

2021
export const batchOperationTypeStringToTypeMap = new Map<
@@ -30,6 +31,8 @@ export const batchOperationTypeStringToTypeMap = new Map<
3031
["SUPERTOKEN_DOWNGRADE", 102],
3132
["SUPERFLUID_CALL_AGREEMENT", 201],
3233
["CALL_APP_ACTION", 202],
34+
["SIMPLE_FORWARD_CALL", 301],
35+
["ERC2771_FORWARD_CALL", 302],
3336
]);
3437

3538
/**
@@ -89,10 +92,25 @@ export default class BatchCall {
8992
const operationStructArray = await Promise.all(
9093
this.getOperationStructArrayPromises
9194
);
92-
const tx =
93-
this.host.contract.populateTransaction.batchCall(
94-
operationStructArray
95-
);
95+
96+
const values = operationStructArray
97+
.filter((x) => x.value?.gt(BigNumber.from(0)))
98+
.map((x) => x.value);
99+
100+
if (values.length > 1) {
101+
throw new SFError({
102+
type: "BATCH_CALL_ERROR",
103+
message:
104+
"There are multiple values in the batch call. The value can only be forwarded to one receiving operation.",
105+
});
106+
}
107+
108+
const tx = this.host.contract.populateTransaction.batchCall(
109+
operationStructArray,
110+
{
111+
value: values?.length > 0 ? values[0] : undefined,
112+
}
113+
);
96114
return new Operation(tx, "UNSUPPORTED");
97115
}
98116

packages/sdk-core/src/Operation.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ export type BatchOperationType =
2020
| "SUPERTOKEN_UPGRADE" // 101
2121
| "SUPERTOKEN_DOWNGRADE" // 102
2222
| "SUPERFLUID_CALL_AGREEMENT" // 201
23-
| "CALL_APP_ACTION"; // 202
23+
| "CALL_APP_ACTION" // 202
24+
| "SIMPLE_FORWARD_CALL" // 301
25+
| "ERC2771_FORWARD_CALL"; // 302
2426

2527
/**
2628
* Operation Helper Class
@@ -164,6 +166,7 @@ export default class Operation {
164166
operationType: batchOperationType,
165167
target: functionArgs["agreementClass"],
166168
data,
169+
value: populatedTransaction.value,
167170
};
168171
}
169172

@@ -178,14 +181,28 @@ export default class Operation {
178181
operationType: batchOperationType,
179182
target: functionArgs["app"],
180183
data: functionArgs["callData"],
184+
value: populatedTransaction.value,
181185
};
182186
}
183187

184-
// Handles remaining ERC20/ERC777/SuperToken Operations
188+
if (
189+
this.type === "SIMPLE_FORWARD_CALL" ||
190+
this.type === "ERC2771_FORWARD_CALL"
191+
) {
192+
return {
193+
operationType: batchOperationType,
194+
target: populatedTransaction.to,
195+
data: populatedTransaction.data,
196+
value: populatedTransaction.value,
197+
};
198+
}
199+
200+
// Handles remaining ERC20/ERC777/SuperToken Operations (including SIMPLE_FORWARD_CALL and ERC2771_FORWARD_CALL)
185201
return {
186202
operationType: batchOperationType,
187203
target: populatedTransaction.to,
188204
data: removeSigHashFromCallData(populatedTransaction.data),
205+
value: populatedTransaction.value,
189206
};
190207
};
191208
}

packages/sdk-core/test/2_operation.test.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { SuperAppTester } from "../typechain-types";
99
import { SuperAppTester__factory } from "../typechain-types";
1010
const cfaInterface = IConstantFlowAgreementV1__factory.createInterface();
1111
import { TestEnvironment, makeSuite } from "./TestEnvironment";
12-
import { ethers } from "ethers";
12+
import { BigNumber, ethers } from "ethers";
1313
import multiplyGasLimit from "../src/multiplyGasLimit";
1414

1515
/**
@@ -174,6 +174,33 @@ makeSuite("Operation Tests", (testEnv: TestEnvironment) => {
174174
expect(txn.gasLimit).to.equal("500000");
175175
});
176176

177+
it("Should move ETH value passed from the Overrides", async () => {
178+
const value = BigNumber.from(Number(0.1e18).toString());
179+
180+
const beforeBalanceAlice = await testEnv.alice.getBalance();
181+
const beforeBalanceBob = await testEnv.bob.getBalance();
182+
183+
expect(beforeBalanceAlice.gt(value)).to.be.true;
184+
185+
const operation = testEnv.sdkFramework.operation(
186+
testEnv.alice.populateTransaction({
187+
to: testEnv.bob.address,
188+
value
189+
}) as Promise<ethers.PopulatedTransaction>,
190+
"SIMPLE_FORWARD_CALL"
191+
);
192+
193+
const txn = await operation.exec(testEnv.alice, 2);
194+
const txReceipt = await txn.wait();
195+
expect(txReceipt.status).to.equal(1);
196+
197+
const afterBalanceAlice = await testEnv.alice.getBalance();
198+
const afterBalanceBob = await testEnv.bob.getBalance();
199+
200+
expect(afterBalanceBob.sub(beforeBalanceBob)).to.equal(value);
201+
expect(beforeBalanceAlice.sub(afterBalanceAlice).gte(value)).to.be.true;
202+
});
203+
177204
it("Should throw an error when trying to execute a transaction with faulty callData", async () => {
178205
const callData = cfaInterface.encodeFunctionData("createFlow", [
179206
testEnv.wrapperSuperToken.address,

packages/sdk-core/test/3_batch_call.test.ts

Lines changed: 114 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ import { expect } from "chai";
22
import {
33
AUTHORIZE_FULL_CONTROL,
44
Operation,
5+
TestToken,
6+
TestToken__factory,
57
getPerSecondFlowRateByMonth,
68
toBN,
79
} from "../src";
8-
import { ethers } from "ethers";
10+
import { BigNumber, Contract, ethers } from "ethers";
911
import { createCallAppActionOperation } from "./2_operation.test";
1012
import { TestEnvironment, makeSuite } from "./TestEnvironment";
1113

@@ -408,4 +410,115 @@ makeSuite("Batch Call Tests", (testEnv: TestEnvironment) => {
408410
"0"
409411
);
410412
});
413+
414+
415+
it("Should be able to execute SimpleForwarder batch call", async () => {
416+
const contract = (new Contract(
417+
testEnv.wrapperSuperToken.underlyingToken.address,
418+
TestToken__factory.abi
419+
) as TestToken).connect(testEnv.alice);
420+
421+
const beforeBalance = await contract.balanceOf(testEnv.alice.address);
422+
423+
const txn1 = contract
424+
.populateTransaction.mint(
425+
testEnv.alice.address,
426+
"100"
427+
);
428+
const txn2 = contract
429+
.populateTransaction.mint(
430+
testEnv.alice.address,
431+
"200"
432+
);
433+
434+
const operation1 = testEnv.sdkFramework.operation(
435+
txn1,
436+
"SIMPLE_FORWARD_CALL"
437+
);
438+
const operation2 = testEnv.sdkFramework.operation(
439+
txn2,
440+
"SIMPLE_FORWARD_CALL"
441+
);
442+
443+
const batchCall = testEnv.sdkFramework.batchCall(
444+
[
445+
operation1,
446+
operation2
447+
]
448+
);
449+
450+
const txResponse = await batchCall.exec(testEnv.alice);
451+
const txReceipt = await txResponse.wait();
452+
expect(txReceipt.status).to.equal(1);
453+
454+
const afterBalance = await contract.balanceOf(testEnv.alice.address);
455+
456+
expect(beforeBalance.lt(afterBalance)).to.be.true;
457+
expect(afterBalance.sub(BigNumber.from(300)).eq(beforeBalance)).to.be.true;
458+
});
459+
460+
it("Should be able to move ETH value", async () => {
461+
const value = BigNumber.from(Number(0.1e18).toString());
462+
463+
const beforeBalanceAlice = await testEnv.alice.getBalance();
464+
const beforeBalanceBob = await testEnv.bob.getBalance();
465+
466+
expect(beforeBalanceAlice.gt(value)).to.be.true;
467+
468+
const operation = testEnv.sdkFramework.operation(
469+
testEnv.alice.populateTransaction({
470+
to: testEnv.bob.address,
471+
value,
472+
data: "0x"
473+
}) as Promise<ethers.PopulatedTransaction>,
474+
"SIMPLE_FORWARD_CALL"
475+
);
476+
477+
const batchCall = testEnv.sdkFramework.batchCall(
478+
[
479+
operation
480+
]
481+
);
482+
483+
const txn = await batchCall.exec(testEnv.alice, 2);
484+
485+
const txReceipt = await txn.wait();
486+
expect(txReceipt.status).to.equal(1);
487+
488+
const afterBalanceAlice = await testEnv.alice.getBalance();
489+
const afterBalanceBob = await testEnv.bob.getBalance();
490+
491+
expect(afterBalanceBob.sub(beforeBalanceBob)).to.equal(value);
492+
expect(beforeBalanceAlice.sub(afterBalanceAlice).gte(value)).to.be.true;
493+
});
494+
495+
it("Should throw an error when multiple Operations with ETH value", async () => {
496+
const operation1 = testEnv.sdkFramework.operation(
497+
testEnv.alice.populateTransaction({
498+
to: testEnv.bob.address,
499+
value: BigNumber.from(Number(0.1e18).toString()),
500+
data: "0x"
501+
}) as Promise<ethers.PopulatedTransaction>,
502+
"SIMPLE_FORWARD_CALL"
503+
);
504+
505+
const operation2 = testEnv.sdkFramework.operation(
506+
testEnv.alice.populateTransaction({
507+
to: testEnv.bob.address,
508+
value: BigNumber.from(Number(0.2e18).toString()),
509+
data: "0x"
510+
}) as Promise<ethers.PopulatedTransaction>,
511+
"SIMPLE_FORWARD_CALL"
512+
);
513+
514+
const batchCall = testEnv.sdkFramework.batchCall(
515+
[
516+
operation1,
517+
operation2
518+
]
519+
);
520+
521+
await expect(batchCall.exec(testEnv.alice, 2))
522+
.to.be.rejectedWith("multiple values in the batch call");
523+
});
411524
});

packages/subgraph/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"dependencies": {
66
"@graphprotocol/graph-cli": "0.80.1",
77
"@graphprotocol/graph-ts": "0.35.1",
8-
"@superfluid-finance/sdk-core": "0.8.0",
8+
"@superfluid-finance/sdk-core": "0.9.0",
99
"mustache": "4.2.0"
1010
},
1111
"devDependencies": {

yarn.lock

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6446,10 +6446,10 @@ browserify-zlib@~0.2.0:
64466446
dependencies:
64476447
pako "~1.0.5"
64486448

6449-
6450-
version "17.0.0"
6451-
resolved "https://registry.yarnpkg.com/browserify/-/browserify-17.0.0.tgz#4c48fed6c02bfa2b51fd3b670fddb805723cdc22"
6452-
integrity sha512-SaHqzhku9v/j6XsQMRxPyBrSP3gnwmE27gLJYZgMT2GeK3J0+0toN+MnuNYDfHwVGQfLiMZ7KSNSIXHemy905w==
6449+
browserify@^17.0.1:
6450+
version "17.0.1"
6451+
resolved "https://registry.yarnpkg.com/browserify/-/browserify-17.0.1.tgz#d822fa701431ca94cb405b033bb2551951e5d97d"
6452+
integrity sha512-pxhT00W3ylMhCHwG5yfqtZjNnFuX5h2IJdaBfSo4ChaaBsIp9VLrEMQ1bHV+Xr1uLPXuNDDM1GlJkjli0qkRsw==
64536453
dependencies:
64546454
JSONStream "^1.0.3"
64556455
assert "^1.4.0"
@@ -6468,7 +6468,7 @@ [email protected]:
64686468
duplexer2 "~0.1.2"
64696469
events "^3.0.0"
64706470
glob "^7.1.0"
6471-
has "^1.0.0"
6471+
hasown "^2.0.0"
64726472
htmlescape "^1.1.0"
64736473
https-browserify "^1.0.0"
64746474
inherits "~2.0.1"
@@ -11083,7 +11083,7 @@ [email protected]:
1108311083
resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
1108411084
integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==
1108511085

11086-
has@^1.0.0, has@^1.0.3:
11086+
has@^1.0.3:
1108711087
version "1.0.3"
1108811088
resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
1108911089
integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==

0 commit comments

Comments
 (0)