From 120d58f37567d1efd4df78a29d84a5012bf5cf18 Mon Sep 17 00:00:00 2001 From: Andrew Richardson Date: Mon, 2 Dec 2024 10:00:03 -0500 Subject: [PATCH] Use a base ledger Atom in bond test Signed-off-by: Andrew Richardson --- .../paladin/pente/domain/BondTest.java | 57 +++++++++++++++++-- .../pente/domain/helpers/NotoHelper.java | 4 +- .../pente/domain/helpers/PenteHelper.java | 2 +- .../contracts/private/BondSubscription.sol | 22 ++++++- solidity/contracts/shared/Atom.sol | 12 ++-- 5 files changed, 82 insertions(+), 15 deletions(-) diff --git a/domains/pente/src/test/java/io/kaleido/paladin/pente/domain/BondTest.java b/domains/pente/src/test/java/io/kaleido/paladin/pente/domain/BondTest.java index 2c885ae7b..ee1922e16 100644 --- a/domains/pente/src/test/java/io/kaleido/paladin/pente/domain/BondTest.java +++ b/domains/pente/src/test/java/io/kaleido/paladin/pente/domain/BondTest.java @@ -138,6 +138,21 @@ void testBond() throws Exception { "contracts/shared/BondTrackerPublic.sol/BondTrackerPublic.json", "abi" ); + String atomFactoryBytecode = ResourceLoader.jsonResourceEntryText( + this.getClass().getClassLoader(), + "contracts/shared/Atom.sol/AtomFactory.json", + "bytecode" + ); + JsonABI atomFactoryABI = JsonABI.fromJSONResourceEntry( + this.getClass().getClassLoader(), + "contracts/shared/Atom.sol/AtomFactory.json", + "abi" + ); + JsonABI atomABI = JsonABI.fromJSONResourceEntry( + this.getClass().getClassLoader(), + "contracts/shared/Atom.sol/Atom.json", + "abi" + ); GroupTupleJSON issuerCustodianGroup = new GroupTupleJSON( JsonHex.randomBytes32(), new String[]{bondIssuer, bondCustodian}); @@ -218,12 +233,20 @@ void testBond() throws Exception { var investorRegistry = bondTracker.investorRegistry(bondCustodian); investorRegistry.addInvestor(bondCustodian, aliceAddress); + // Create the atom factory on the base ledger + String atomFactoryAddress = testbed.getRpcClient().request("testbed_deployBytecode", + "issuer", + atomFactoryABI, + atomFactoryBytecode, + new HashMap()); + // Alice deploys BondSubscription to the alice/custodian privacy group, to request subscription // TODO: if Alice deploys, how can custodian trust it's the correct logic? var bondSubscription = BondSubscriptionHelper.deploy(aliceCustodianInstance, alice, new HashMap<>() {{ put("bondAddress_", notoBond.address()); put("units_", 1000); put("custodian_", custodianAddress); + put("atomFactory_", atomFactoryAddress); }}); // Prepare the bond transfer (requires 2 calls to prepare, as the Noto transaction spawns a Pente transaction to wrap it) @@ -249,25 +272,51 @@ void testBond() throws Exception { bondSubscription.prepareBond(bondCustodian, bondTransfer2.preparedTransaction().to(), bondTransferMetadata.transitionWithApproval().encodedCall()); bondSubscription.preparePayment(alice, paymentTransfer.preparedTransaction().to(), paymentMetadata.transferWithApproval().encodedCall()); + // Alice receives full bond distribution + bondSubscription.distribute(bondCustodian, 1000); + + // Look up the deployed Atom + // TODO: use the AtomDeployed event instead of this method + HashMap queryResult = testbed.getRpcClient().request("ptx_call", + new Testbed.TransactionInput( + "public", + "", + bondCustodian, + JsonHex.addressFrom(atomFactoryAddress), + new HashMap<>(), + atomFactoryABI, + "lastDeploy")); + var atomAddress = JsonHex.addressFrom(queryResult.get("0").toString()); + // Alice approves payment transfer notoCash.approveTransfer( "alice", paymentTransfer.inputStates(), paymentTransfer.outputStates(), paymentMetadata.approvalParams().data(), - aliceCustodianInstance.address()); + atomAddress.toString()); // Custodian approves bond transfer issuerCustodianInstance.approveTransition( bondCustodian, JsonHex.randomBytes32(), - aliceCustodianInstance.address(), + atomAddress, bondTransferMetadata.approvalParams().transitionHash(), bondTransferMetadata.approvalParams().signatures()); Thread.sleep(3000); // TODO: wait for transaction receipt - // Alice receives full bond distribution - bondSubscription.distribute(bondCustodian, 1000); + // Execute the Atom + testbed.getRpcClient().request("ptx_sendTransaction", + new Testbed.TransactionInput( + "public", + "", + bondCustodian, + atomAddress, + new HashMap<>(), + atomABI, + "execute" + )); + Thread.sleep(3000); // TODO: wait for transaction receipt // TODO: figure out how to test negative cases (such as when Pente reverts due to a non-allowed investor) diff --git a/domains/pente/src/test/java/io/kaleido/paladin/pente/domain/helpers/NotoHelper.java b/domains/pente/src/test/java/io/kaleido/paladin/pente/domain/helpers/NotoHelper.java index 0086cbd88..928743311 100644 --- a/domains/pente/src/test/java/io/kaleido/paladin/pente/domain/helpers/NotoHelper.java +++ b/domains/pente/src/test/java/io/kaleido/paladin/pente/domain/helpers/NotoHelper.java @@ -81,7 +81,7 @@ public record ApproveExtraParams( } @JsonIgnoreProperties(ignoreUnknown = true) - public record NotoPublicTransaction( + public record PublicTransaction( @JsonProperty JsonABI.Entry functionABI, @JsonProperty @@ -96,7 +96,7 @@ public record NotoTransferMetadata( @JsonProperty ApproveExtraParams approvalParams, @JsonProperty - NotoPublicTransaction transferWithApproval + PublicTransaction transferWithApproval ) { } diff --git a/domains/pente/src/test/java/io/kaleido/paladin/pente/domain/helpers/PenteHelper.java b/domains/pente/src/test/java/io/kaleido/paladin/pente/domain/helpers/PenteHelper.java index 8ce0c217f..61fc8f0bc 100644 --- a/domains/pente/src/test/java/io/kaleido/paladin/pente/domain/helpers/PenteHelper.java +++ b/domains/pente/src/test/java/io/kaleido/paladin/pente/domain/helpers/PenteHelper.java @@ -205,7 +205,7 @@ public Testbed.TransactionResult prepare(String sender, JsonABI.Entry fn, Map signatures) throws IOException { + public void approveTransition(String sender, JsonHex.Bytes32 txID, JsonHex.Address delegate, JsonHex.Bytes32 transitionHash, List signatures) throws IOException { JsonABI.Entry fn = JsonABI.newFunction( "approveTransition", JsonABI.newParameters( diff --git a/solidity/contracts/private/BondSubscription.sol b/solidity/contracts/private/BondSubscription.sol index 54dee0435..b465abf2a 100644 --- a/solidity/contracts/private/BondSubscription.sol +++ b/solidity/contracts/private/BondSubscription.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.20; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {IPenteExternalCall} from "./interfaces/IPenteExternalCall.sol"; +import {Atom, AtomFactory} from "../shared/Atom.sol"; /** * @title BondSubscription @@ -13,6 +14,7 @@ contract BondSubscription is Ownable, IPenteExternalCall { address public custodian; uint256 public requestedUnits; uint256 public receivedUnits; + address public atomFactory; address internal distributeBondAddress; bytes internal distributeBondCall; @@ -27,11 +29,13 @@ contract BondSubscription is Ownable, IPenteExternalCall { constructor( address bondAddress_, uint256 units_, - address custodian_ + address custodian_, + address atomFactory_ ) Ownable(_msgSender()) { bondAddress = bondAddress_; requestedUnits = units_; custodian = custodian_; + atomFactory = atomFactory_; } function preparePayment( @@ -65,7 +69,19 @@ contract BondSubscription is Ownable, IPenteExternalCall { "Payment transfer has not been prepared" ); receivedUnits += units_; - emit PenteExternalCall(distributeBondAddress, distributeBondCall); - emit PenteExternalCall(distributePaymentAddress, distributePaymentCall); + + Atom.Operation[] memory operations = new Atom.Operation[](2); + operations[0] = Atom.Operation( + distributeBondAddress, + distributeBondCall + ); + operations[1] = Atom.Operation( + distributePaymentAddress, + distributePaymentCall + ); + emit PenteExternalCall( + atomFactory, + abi.encodeCall(AtomFactory.create, operations) + ); } } diff --git a/solidity/contracts/shared/Atom.sol b/solidity/contracts/shared/Atom.sol index 6aa384422..7079eab19 100644 --- a/solidity/contracts/shared/Atom.sol +++ b/solidity/contracts/shared/Atom.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.20; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; -import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; -import "../domains/interfaces/INoto.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import {INoto} from "../domains/interfaces/INoto.sol"; contract Atom is Initializable { uint256 private _operationCount; @@ -66,6 +66,7 @@ contract Atom is Initializable { contract AtomFactory { address public immutable logic; + address public lastDeploy; // TODO: remove and listen to AtomDeployed event AtomDeployed(address addr); @@ -84,6 +85,7 @@ contract AtomFactory { address addr = address( new ERC1967Proxy(logic, _initializationCalldata) ); + lastDeploy = addr; emit AtomDeployed(addr); } }