Skip to content

Commit

Permalink
perf(contract)!: don't depend on Contract in AeSdk
Browse files Browse the repository at this point in the history
BREAKING CHANGE: `AeSdk:initializeContract` removed
Use `Contract.initialize` instead:
```diff
- aeSdk.initializeContract(options)
+ Contract.initialize({ ...aeSdk.getContext(), ...options })
```
  • Loading branch information
davidyuk committed May 29, 2024
1 parent 10a97f1 commit 00b4f86
Show file tree
Hide file tree
Showing 24 changed files with 191 additions and 115 deletions.
21 changes: 15 additions & 6 deletions docs/guides/contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,26 +53,28 @@ Note:

## 4. Initialize the contract instance

To do so, we need to prepare an options object, which can be done in multiple ways.

### By source code

```js
const sourceCode = ... // source code of the contract
const contract = await aeSdk.initializeContract({ sourceCode })
const options = { sourceCode }
```

Note:

- If your contract includes external dependencies which are not part of the [standard library](https://docs.aeternity.com/aesophia/latest/sophia_stdlib) you should initialize the contract using:
```js
const fileSystem = ... // key-value map with name of the include as key and source code of the include as value
const contract = await aeSdk.initializeContract({ sourceCode, fileSystem })
const options = { sourceCode, fileSystem }
```

### By path to source code (available only in Node.js)
It can be used with both CompilerCli and CompilerHttp. This way contract imports would be handled automatically, with no need to provide `fileSystem` option.
```js
const sourceCodePath = './example.aes'
const contract = await aeSdk.initializeContract({ sourceCodePath })
const options = { sourceCodePath }
```

### By ACI and bytecode
Expand All @@ -81,7 +83,7 @@ If you pre-compiled the contracts you can also initialize a contract instance by
```js
const aci = ... // ACI of the contract
const bytecode = ... // bytecode of the contract
const contract = await aeSdk.initializeContract({ aci, bytecode })
const options = { aci, bytecode }
```

### By ACI and contract address
Expand All @@ -90,12 +92,19 @@ In many cases an application doesn't need to deploy a contract or verify its byt
```js
const aci = ... // ACI of the contract
const address = ... // the address of the contract
const contract = await aeSdk.initializeContract({ aci, address })
const options = { aci, address }
```

### Create contract instance
Do it by `Contract::initialize`.
```js
const contract = await Contract.initialize({ ...aeSdk.getContext(), ...options })
```
`AeSdk:getContext` is used to get base options to instantiate contracts. These options include the current account, node, and compiler. They are referenced using Proxy class, pointing to the latest values specified in AeSdk. So, if you change the selected node in the AeSdk instance, it will be also changed in bound contract instances.

### Options

- Following attributes can be provided via `options` to `initializeContract`:
- Following attributes can be provided via `options` to `Contract::initialize`:
- `aci` (default: obtained via `onCompiler`)
- The Contract ACI.
- `address`
Expand Down
5 changes: 4 additions & 1 deletion examples/browser/aepp/src/Contracts.vue
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
<script>
import { shallowRef } from 'vue';
import { mapState } from 'vuex';
import { Contract } from '@aeternity/aepp-sdk';
import Value from './components/Value.vue';
import FieldAction from './components/FieldAction.vue';
Expand Down Expand Up @@ -95,7 +96,9 @@ export default {
async create() {
// Contract instance can't be in deep reactive https://github.com/aeternity/aepp-sdk-js/blob/develop/docs/README.md#vue3
this.contract = shallowRef(
await this.aeSdk.initializeContract({ sourceCode: this.contractSourceCode }),
await Contract.initialize({
...this.aeSdk.getContext(), sourceCode: this.contractSourceCode,
}),
);
},
async compile() {
Expand Down
6 changes: 4 additions & 2 deletions examples/node/contract-interaction.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
//
// You need to import `AeSdk`, `Node` and `MemoryAccount` classes from the SDK.
import {
AeSdk, CompilerHttp, Node, MemoryAccount,
AeSdk, Contract, CompilerHttp, Node, MemoryAccount,
} from '@aeternity/aepp-sdk';

// **Note**:
Expand Down Expand Up @@ -55,7 +55,9 @@ const aeSdk = new AeSdk({
// Knowing the source code allows you to initialize a contract instance and interact with the
// contract in a convenient way.
console.log(CONTRACT_SOURCE_CODE);
const contract = await aeSdk.initializeContract({ sourceCode: CONTRACT_SOURCE_CODE });
const contract = await Contract.initialize({
...aeSdk.getOptions(), sourceCode: CONTRACT_SOURCE_CODE,
});

// ## 5. Compile the contract
// The `$compile` function sends a raw Sophia contract as string
Expand Down
8 changes: 4 additions & 4 deletions examples/node/paying-for-contract-call-tx.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
// You need to import `AeSdk`, `Node` and `MemoryAccount` classes from the SDK.
// Additionally you import the `generateKeyPair` utility function to generate a new keypair.
import {
AeSdk, CompilerHttp, Node, MemoryAccount, Tag,
AeSdk, Contract, CompilerHttp, Node, MemoryAccount, Tag,
} from '@aeternity/aepp-sdk';

// **Note**:
Expand Down Expand Up @@ -100,9 +100,9 @@ const aeSdk = new AeSdk({
// 1. Sign the transaction by providing `innerTx: true` as transaction option.
// - The transaction will be signed in a special way that is required for inner transactions.
//
const contract = await aeSdk.initializeContract(
{ sourceCode: CONTRACT_SOURCE_CODE, address: CONTRACT_ADDRESS },
);
const contract = await Contract.initialize({
...aeSdk.getContext(), sourceCode: CONTRACT_SOURCE_CODE, address: CONTRACT_ADDRESS,
});
const calldata = contract._calldata.encode('PayingForTxExample', 'set_last_caller', []);
const contractCallTx = await aeSdk.buildTx({
tag: Tag.ContractCallTx,
Expand Down
7 changes: 0 additions & 7 deletions src/AeSdkMethods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { sendTransaction } from './send-transaction';
import * as aensMethods from './aens';
import * as spendMethods from './spend';
import * as oracleMethods from './oracle';
import Contract, { ContractMethodsBase } from './contract/Contract';
import createDelegationSignature from './contract/delegation-signature';
import * as contractGaMethods from './contract/ga';
import { buildTxAsync } from './tx/builder';
Expand Down Expand Up @@ -93,12 +92,6 @@ class AeSdkMethods {
async buildTx(options: TxParamsAsync): Promise<Encoded.Transaction> {
return buildTxAsync({ ...this.getContext(), ...options });
}

async initializeContract<Methods extends ContractMethodsBase>(
options?: Omit<Parameters<typeof Contract.initialize>[0], 'onNode'> & { onNode?: Node },
): Promise<Contract<Methods>> {
return Contract.initialize<Methods>(this.getContext(options as AeSdkMethodsOptions));
}
}

type RequiredKeys<T> = {
Expand Down
2 changes: 1 addition & 1 deletion src/contract/Contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ interface GetCallResultByHashReturnType<M extends ContractMethodsBase, Fn extend
* @returns JS Contract API
* @example
* ```js
* const contractIns = await aeSdk.initializeContract({ sourceCode })
* const contractIns = await Contract.initialize({ ...aeSdk.getContext(), sourceCode })
* await contractIns.$deploy([321]) or await contractIns.init(321)
* const callResult = await contractIns.$call('setState', [123])
* const staticCallResult = await contractIns.$call('setState', [123], { callStatic: true })
Expand Down
8 changes: 6 additions & 2 deletions test/environment/browser-aepp.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
Open developer console
<script src="../../dist/aepp-sdk.browser-script.js"></script>
<script type="text/javascript">
const { Node, AeSdkAepp, CompilerHttp, walletDetector, BrowserWindowMessageConnection } = Aeternity;
const {
Node, AeSdkAepp, CompilerHttp, walletDetector, BrowserWindowMessageConnection, Contract,
} = Aeternity;

const contractSourceCode = `
contract Test =
Expand Down Expand Up @@ -52,7 +54,9 @@
console.log('Height:', await aeSdk.getHeight());
console.log('Instanceof works correctly for nodes pool', aeSdk.pool instanceof Map);

const contract = await aeSdk.initializeContract({ sourceCode: contractSourceCode });
const contract = await Contract.initialize({
...aeSdk.getContext(), sourceCode: contractSourceCode,
});
const deployInfo = await contract.$deploy([]);
console.log('Contract deployed at', deployInfo.address);
const map = new Map([['foo', 42], ['bar', 43]]);
Expand Down
6 changes: 4 additions & 2 deletions test/environment/browser.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
Open developer console
<script src="../../dist/aepp-sdk.browser-script.js"></script>
<script type="text/javascript">
const { Node, AeSdk, MemoryAccount, CompilerHttp } = Aeternity;
const { Node, AeSdk, MemoryAccount, CompilerHttp, Contract } = Aeternity;

const contractSourceCode = `
contract Test =
Expand All @@ -27,7 +27,9 @@
console.log('Height:', await aeSdk.getHeight());
console.log('Instanceof works correctly for nodes pool', aeSdk.pool instanceof Map);

const contract = await aeSdk.initializeContract({ sourceCode: contractSourceCode });
const contract = await Contract.initialize({
...aeSdk.getContext(), sourceCode: contractSourceCode,
});
const deployInfo = await contract.$deploy([]);
console.log('Contract deployed at', deployInfo.address);
const map = new Map([['foo', 42], ['bar', 43]]);
Expand Down
6 changes: 4 additions & 2 deletions test/environment/ledger/main.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {
Node, AeSdk, CompilerHttp, AccountLedgerFactory,
Node, AeSdk, CompilerHttp, AccountLedgerFactory, Contract,
// eslint-disable-next-line import/extensions
} from '../../../es/index.mjs';

Expand Down Expand Up @@ -27,7 +27,9 @@ export default async function run(transport) {
contract Test =
entrypoint getArg(x : int) = x + 1
`;
const contract = await aeSdk.initializeContract({ sourceCode: contractSourceCode });
const contract = await Contract.initialize({
...aeSdk.getContext(), sourceCode: contractSourceCode,
});
const deployInfo = await contract.$deploy([]);
console.log('Contract deployed at', deployInfo.address);

Expand Down
6 changes: 4 additions & 2 deletions test/environment/node.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env node
const {
Node, AeSdk, MemoryAccount, CompilerHttp,
Node, AeSdk, MemoryAccount, CompilerHttp, Contract,
// eslint-disable-next-line @typescript-eslint/no-var-requires
} = require('../../dist/aepp-sdk');

Expand All @@ -21,7 +21,9 @@ const aeSdk = new AeSdk({
console.log('Height:', await aeSdk.getHeight());
console.log('Instanceof works correctly for nodes pool', aeSdk.pool instanceof Map);

const contract = await aeSdk.initializeContract({ sourceCode: contractSourceCode });
const contract = await Contract.initialize({
...aeSdk.getContext(), sourceCode: contractSourceCode,
});
const deployInfo = await contract.$deploy([]);
console.log('Contract deployed at', deployInfo.address);
const map = new Map([['foo', 42], ['bar', 43]]);
Expand Down
6 changes: 4 additions & 2 deletions test/environment/node.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env node
import {
Node, AeSdk, MemoryAccount, CompilerHttp,
Node, AeSdk, MemoryAccount, CompilerHttp, Contract,
// eslint-disable-next-line import/extensions
} from '../../es/index.mjs';

Expand All @@ -20,7 +20,9 @@ const aeSdk = new AeSdk({
console.log('Height:', await aeSdk.getHeight());
console.log('Instanceof works correctly for nodes pool', aeSdk.pool instanceof Map);

const contract = await aeSdk.initializeContract({ sourceCode: contractSourceCode });
const contract = await Contract.initialize({
...aeSdk.getContext(), sourceCode: contractSourceCode,
});
const deployInfo = await contract.$deploy([]);
console.log('Contract deployed at', deployInfo.address);
const map = new Map([['foo', 42], ['bar', 43]]);
Expand Down
6 changes: 3 additions & 3 deletions test/environment/node.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env npx ts-node
import {
Node, AeSdk, MemoryAccount, CompilerHttp,
Node, AeSdk, MemoryAccount, CompilerHttp, Contract,
} from '../..';

const contractSourceCode = `
Expand All @@ -20,9 +20,9 @@ const aeSdk = new AeSdk({
console.log('Height:', await aeSdk.getHeight());
console.log('Instanceof works correctly for nodes pool', aeSdk.pool instanceof Map);

const contract = await aeSdk.initializeContract<{
const contract = await Contract.initialize<{
getArg: (x: Map<string, number | bigint | string>) => Map<string, bigint>;
}>({ sourceCode: contractSourceCode });
}>({ ...aeSdk.getContext(), sourceCode: contractSourceCode });
const deployInfo = await contract.$deploy([]);
console.log('Contract deployed at', deployInfo.address);
const map = new Map([['foo', 42], ['bar', 43]]);
Expand Down
5 changes: 3 additions & 2 deletions test/integration/AeSdkMethods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { describe, it, before } from 'mocha';
import { expect } from 'chai';
import { getSdk } from '.';
import { assertNotNull } from '../utils';
import { AeSdkMethods, AccountBase } from '../../src';
import { AeSdkMethods, AccountBase, Contract } from '../../src';

describe('AeSdkMethods', () => {
let accounts: AccountBase[];
Expand All @@ -26,7 +26,8 @@ describe('AeSdkMethods', () => {
});

it('created contract remains connected to sdk', async () => {
const contract = await aeSdkMethods.initializeContract({
const contract = await Contract.initialize({
...aeSdkMethods.getContext(),
sourceCode: ''
+ 'contract Identity =\n'
+ ' entrypoint getArg(x : int) = x',
Expand Down
7 changes: 5 additions & 2 deletions test/integration/account-generalized.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ describe('Generalized Account', () => {
accountBeforeGa = Object.values(aeSdk.accounts)[0] as MemoryAccount;
const { gaContractId } = await aeSdk.createGeneralizedAccount('authorize', [], { sourceCode });
expect((await aeSdk.getAccount(gaAccountAddress)).kind).to.be.equal('generalized');
authContract = await aeSdk.initializeContract({ sourceCode, address: gaContractId });
authContract = await Contract.initialize({
...aeSdk.getContext(), sourceCode, address: gaContractId,
});
});

it('Fail on make GA on already GA', async () => {
Expand Down Expand Up @@ -129,11 +131,12 @@ describe('Generalized Account', () => {
// TODO: enable after resolving https://github.com/aeternity/aeternity/issues/4087
// TODO: copy to examples/node/account-generalized.mjs
it.skip('deploys and calls contract', async () => {
const contract = await aeSdk.initializeContract<{
const contract = await Contract.initialize<{
init: (value: number) => void;
getState: () => number;
setState: (value: number) => void;
}>({
...aeSdk.getContext(),
sourceCode: ''
+ 'contract Stateful =\n'
+ ' record state = { value: int }\n'
Expand Down
8 changes: 5 additions & 3 deletions test/integration/aens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
assertNotNull, ensureEqual, randomName, randomString,
} from '../utils';
import {
AeSdk, generateKeyPair, buildContractId, computeBidFee, ensureName, produceNameId,
AeSdk, generateKeyPair, buildContractId, computeBidFee, ensureName, produceNameId, Contract,
AensPointerContextError, encode, decode, Encoding, ContractMethodsBase, ConsensusProtocolVersion,
unpackTx, Tag, buildTxHash,
} from '../../src';
Expand Down Expand Up @@ -140,13 +140,15 @@ describe('Aens', () => {
interface ContractApi extends ContractMethodsBase {
getArg: (x: number) => bigint;
}
let contract = await aeSdk.initializeContract<ContractApi>({ sourceCode });
let contract = await Contract.initialize<ContractApi>({ ...aeSdk.getContext(), sourceCode });
await contract.$deploy([]);
const nameObject = await aeSdk.aensQuery(name);
assertNotNull(contract.$options.address);
await nameObject.update({ contract_pubkey: contract.$options.address });

contract = await aeSdk.initializeContract<ContractApi>({ sourceCode, address: name });
contract = await Contract.initialize<ContractApi>({
...aeSdk.getContext(), sourceCode, address: name,
});
expect((await contract.getArg(42, { callStatic: true })).decodedResult).to.be.equal(42n);
expect((await contract.getArg(42, { callStatic: false })).decodedResult).to.be.equal(42n);
});
Expand Down
5 changes: 3 additions & 2 deletions test/integration/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { expect } from 'chai';
import { stub } from 'sinon';
import { getSdk } from '.';
import {
generateKeyPair, AeSdk, Tag, MemoryAccount, Encoded, Node,
generateKeyPair, AeSdk, Tag, MemoryAccount, Encoded, Node, Contract,
} from '../../src';
import { assertNotNull, bindRequestCounter } from '../utils';

Expand Down Expand Up @@ -177,7 +177,8 @@ describe('Node Chain', () => {
it('ensure transactions mined', async () => Promise.all(transactions.map(async (hash) => aeSdkWithoutAccount.poll(hash))));

it('multiple contract dry-runs calls at one request', async () => {
const contract = await aeSdk.initializeContract<{ foo: (x: number) => bigint }>({
const contract = await Contract.initialize<{ foo: (x: number) => bigint }>({
...aeSdk.getContext(),
sourceCode:
'contract Test =\n'
+ ' entrypoint foo(x : int) = x * 100',
Expand Down
4 changes: 3 additions & 1 deletion test/integration/channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -821,7 +821,9 @@ async function waitForChannel(channel: Channel): Promise<void> {
sign: responderSignTag,
});
await Promise.all([waitForChannel(initiatorCh), waitForChannel(responderCh)]);
contract = await aeSdkInitiatior.initializeContract({ sourceCode: contractSourceCode });
contract = await Contract.initialize({
...aeSdkInitiatior.getContext(), sourceCode: contractSourceCode,
});
const initiatorNewContract = sinon.spy();
initiatorCh.on('newContract', initiatorNewContract);
const responderNewContract = sinon.spy();
Expand Down
Loading

0 comments on commit 00b4f86

Please sign in to comment.