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

docs: proxy contract cookbook #3253

Merged
merged 52 commits into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
73b1bb1
docs: cookbook for manually deploying and upgrading a proxy
danielbate Oct 5, 2024
191054d
Merge branch 'master' of https://github.com/FuelLabs/fuels-ts into db…
danielbate Oct 9, 2024
0e795f7
chore: add missing test groups
danielbate Oct 9, 2024
36c0e24
chore: changeset
danielbate Oct 9, 2024
238fd5b
chore: update changeset
danielbate Oct 9, 2024
5459232
chore: forc format
danielbate Oct 9, 2024
8a79ee2
Merge branch 'db/chore/manual-proxy-contracts' of https://github.com/…
danielbate Oct 9, 2024
360b704
chore: update doc
danielbate Oct 9, 2024
d7c0b0c
chore: update doc
danielbate Oct 9, 2024
445c93c
chore: update doc
danielbate Oct 9, 2024
914bc01
Merge branch 'master' into db/chore/manual-proxy-contracts
danielbate Oct 9, 2024
67d9433
Merge branch 'master' into db/chore/manual-proxy-contracts
Torres-ssf Oct 11, 2024
d0ff3df
Merge branch 'master' of https://github.com/FuelLabs/fuels-ts into db…
danielbate Oct 15, 2024
8f9c67c
docs: use src 14 commit hash for doc
danielbate Oct 15, 2024
38a125f
chore: migrate to v2 snippet
danielbate Oct 17, 2024
a17bb8e
chore: restore v1 snippets files
danielbate Oct 17, 2024
dea7aea
chore: fix snippet path
danielbate Oct 17, 2024
93b0472
chore: fix test region
danielbate Oct 17, 2024
2290e74
multilning doc comments
Torres-ssf Oct 17, 2024
b966a26
moving snippet to another place
Torres-ssf Oct 17, 2024
e1c676a
Merge branch 'master' into db/chore/manual-proxy-contracts
Torres-ssf Oct 21, 2024
4c5f549
Merge branch 'master' into db/chore/manual-proxy-contracts
Torres-ssf Oct 22, 2024
3eccadc
Merge branch 'master' into db/chore/manual-proxy-contracts
Torres-ssf Oct 29, 2024
3f4b90e
Merge branch 'master' of https://github.com/FuelLabs/fuels-ts into db…
danielbate Nov 19, 2024
6abfd3b
chore: fix toml
danielbate Nov 19, 2024
94ff50b
Merge branch 'master' of https://github.com/FuelLabs/fuels-ts into db…
danielbate Nov 26, 2024
28df5eb
chore: further snippet migration
danielbate Nov 26, 2024
50c2090
feat: add recipe package and import proxy there
danielbate Nov 26, 2024
1eac25b
chore: changeset
danielbate Nov 26, 2024
f0066a5
chore: fix build script
danielbate Nov 26, 2024
fd7885e
chore: changeset
danielbate Nov 26, 2024
b9af2a9
chore: lint
danielbate Nov 26, 2024
eaf5064
chore: lint
danielbate Nov 26, 2024
36037ac
chore: update changeset
danielbate Nov 26, 2024
bcee8b0
chore: update changeset
danielbate Nov 26, 2024
8c50f3d
chore: revert changeset
danielbate Nov 27, 2024
e6327be
chore: lint
danielbate Nov 27, 2024
f6545e6
chore: ignore linters
danielbate Nov 27, 2024
67441b1
Merge branch 'master' into db/chore/manual-proxy-contracts
danielbate Nov 27, 2024
9965c02
chore: cleanup old docs snips
danielbate Nov 27, 2024
970d19f
Merge branch 'db/chore/manual-proxy-contracts' of https://github.com/…
danielbate Nov 27, 2024
8653567
chore: readme updates
danielbate Nov 27, 2024
b24ccef
chore: update doc
danielbate Nov 27, 2024
c32faff
chore: add changelog
danielbate Nov 27, 2024
a0df28b
Merge branch 'db/chore/manual-proxy-contracts' of https://github.com/…
danielbate Nov 27, 2024
29c1e72
chore: make private
danielbate Nov 28, 2024
ea30938
chore: retrieve slots from factory
danielbate Nov 28, 2024
b0029bd
chore: lint
danielbate Nov 28, 2024
50eee30
chore: simplify readme
danielbate Nov 28, 2024
5a9ad21
chore: revert private change
danielbate Nov 28, 2024
dce8677
chore: improve doc
danielbate Nov 28, 2024
f46fc0c
chore: improve doc
danielbate Nov 28, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/polite-mugs-stare.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fuel-ts/recipes": patch
danielbate marked this conversation as resolved.
Show resolved Hide resolved
danielbate marked this conversation as resolved.
Show resolved Hide resolved
---

docs: proxy contract cookbook
danielbate marked this conversation as resolved.
Show resolved Hide resolved
2 changes: 1 addition & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ apps/create-fuels-counter-guide
apps/docs/src/typegend
apps/docs/src/**/*.test.ts

packages/fuels/src/cli/commands/deploy/proxy
packages/recipes/src
packages/fuels/test/fixtures/project
packages/account/src/providers/__generated__
packages/account/src/providers/assets
Expand Down
2 changes: 1 addition & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ apps/docs/src/api
apps/docs/src/typegend
apps/docs/src/**/*.test.ts

packages/fuels/src/cli/commands/deploy/proxy
packages/recipes/src
packages/fuels/test/fixtures/project
packages/account/src/providers/assets

Expand Down
3 changes: 2 additions & 1 deletion apps/docs-api/typedoc.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
"../../packages/errors",
"../../packages/hasher",
"../../packages/math",
"../../packages/transactions"
"../../packages/transactions",
"../../packages/recipes"
],
"out": "src/api",
"readme": "./index.md",
Expand Down
46 changes: 44 additions & 2 deletions apps/docs/src/guide/contracts/proxy-contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,51 @@

Automatic deployment of proxy contracts can be enabled in `Forc.toml`.

Once that is in place, [fuels deploy](https://docs.fuel.network/docs/fuels-ts/fuels-cli/commands/#fuels-deploy) will take care of it.
We recommend that you use [fuels deploy](https://docs.fuel.network/docs/fuels-ts/fuels-cli/commands/#fuels-deploy) to deploy and upgrade your contract using a proxy as it will take care of everything for you. However, if you want to deploy a proxy contract manually, you can follow the guide below.

## Docs
## Manually Deploying and Upgrading by Proxy
petertonysmith94 marked this conversation as resolved.
Show resolved Hide resolved

As mentioned above, we recommend using [fuels deploy](https://docs.fuel.network/docs/fuels-ts/fuels-cli/commands/#fuels-deploy) to deploy and upgrade your contract as everything is handled under the hood. But the below guide will detail this process should you want to implement it yourself.

We recommend using the [SRC14 compliant owned proxy contract](https://github.com/FuelLabs/sway-standard-implementations/tree/174f5ed9c79c23a6aaf5db906fe27ecdb29c22eb/src14/owned_proxy/contract/out/release) as the underlying proxy as that is the one we will use in this guide and the one used by [fuels deploy](https://docs.fuel.network/docs/fuels-ts/fuels-cli/commands/#fuels-deploy). A TypeScript implementation of this proxy is exported from the `fuels` package as `Src14OwnedProxy` and `Src14OwnedProxyFactory`.

The overall process is as follows:

1. Deploy your contract
1. Deploy the proxy contract
1. Set the target of the proxy contract to your deployed contract
1. Make calls to the contract via the proxy contract ID
1. Upgrade the contract by deploying a new version of the contract and updating the target of the proxy contract

> **Note**: When new storage slots are added to the contract, they must be initialized in the proxy contract before they can be read from. This can be done by first writing to the new storage slot in the proxy contract. Failure to do so will result in the transaction being reverted.

For example, lets imagine we want to deploy the following counter contract:

<<< @/../../docs/sway/counter/src/main.sw#proxy-1{rs:line-numbers}

Let's deploy and interact with it by proxy. First let's setup the environment and deploy the counter contract:

<<< @./snippets/proxy-contracts.ts#proxy-2{ts:line-numbers}

Now let's deploy the [SRC14 compliant proxy contract](https://github.com/FuelLabs/sway-standard-implementations/tree/174f5ed9c79c23a6aaf5db906fe27ecdb29c22eb/src14/owned_proxy/contract/out/release) and initialize it by setting its target to the counter target ID.

<<< @./snippets/proxy-contracts.ts#proxy-3{ts:line-numbers}

Finally, we can call our counter contract using the contract ID of the proxy.

<<< @./snippets/proxy-contracts.ts#proxy-4{ts:line-numbers}

Now let's make some changes to our initial counter contract by adding an additional storage slot to track the number of increments and a new get method that retrieves its value:

<<< @/../../docs/sway/counter-v2/src/main.sw#proxy-5{rs:line-numbers}

We can deploy it and update the target of the proxy like so:

<<< @./snippets/proxy-contracts.ts#proxy-6{ts:line-numbers}

Then, we can instantiate our upgraded contract via the same proxy contract ID:

<<< @./snippets/proxy-contracts.ts#proxy-7{ts:line-numbers}

For more info, please check these docs:

Expand Down
106 changes: 106 additions & 0 deletions apps/docs/src/guide/contracts/snippets/proxy-contracts.ts
petertonysmith94 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// #region proxy-2
import {
Provider,
Wallet,
Src14OwnedProxy,
Src14OwnedProxyFactory,
} from 'fuels';

import { LOCAL_NETWORK_URL, WALLET_PVT_KEY } from '../../../env';
import {
Counter,
CounterFactory,
CounterV2,
CounterV2Factory,
} from '../../../typegend';

const provider = await Provider.create(LOCAL_NETWORK_URL);
const wallet = Wallet.fromPrivateKey(WALLET_PVT_KEY, provider);

const counterContractFactory = new CounterFactory(wallet);
const deploy = await counterContractFactory.deploy();
const { contract: counterContract } = await deploy.waitForResult();
// #endregion proxy-2

// #region proxy-3
/**
* It is important to pass all storage slots to the proxy in order to
* initialize the storage slots.
*/
const storageSlots = counterContractFactory.storageSlots.concat(
Src14OwnedProxy.storageSlots
);
/**
* These configurables are specific to our recommended SRC14 compliant
* contract. They must be passed on deployment and then `initialize_proxy`
* must be called to setup the proxy contract.
*/
const configurableConstants = {
INITIAL_TARGET: { bits: counterContract.id.toB256() },
INITIAL_OWNER: {
Initialized: { Address: { bits: wallet.address.toB256() } },
},
};

const proxyContractFactory = new Src14OwnedProxyFactory(wallet);
const proxyDeploy = await proxyContractFactory.deploy({
storageSlots,
configurableConstants,
});

const { contract: proxyContract } = await proxyDeploy.waitForResult();
const { waitForResult } = await proxyContract.functions
.initialize_proxy()
.call();

await waitForResult();
// #endregion proxy-3

// #region proxy-4
/**
* Make sure to use only the contract ID of the proxy when instantiating
* the contract as this will remain static even with future upgrades.
*/
const proxiedContract = new Counter(proxyContract.id, wallet);

const incrementCall = await proxiedContract.functions.increment_count(1).call();
await incrementCall.waitForResult();

const { value: count } = await proxiedContract.functions.get_count().get();
// #endregion proxy-4

console.log('count:', count.toNumber() === 1);

// #region proxy-6
const deployV2 = await CounterV2Factory.deploy(wallet);
const { contract: contractV2 } = await deployV2.waitForResult();

const updateTargetCall = await proxyContract.functions
.set_proxy_target({ bits: contractV2.id.toB256() })
.call();

await updateTargetCall.waitForResult();
// #endregion proxy-6

// #region proxy-7
/**
* Again, we are instantiating the contract with the same proxy ID
* but using a new contract instance.
*/
const upgradedContract = new CounterV2(proxyContract.id, wallet);

const incrementCall2 = await upgradedContract.functions
.increment_count(1)
.call();

await incrementCall2.waitForResult();

const { value: increments } = await upgradedContract.functions
.get_increments()
.get();

const { value: count2 } = await upgradedContract.functions.get_count().get();
// #endregion proxy-7

console.log('secondCount', count2.toNumber() === 2);
console.log('increments', increments);
1 change: 1 addition & 0 deletions apps/docs/sway/Forc.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ members = [
"call-test-script",
"configurable-pin",
"counter",
"counter-v2",
"echo-asset-id",
"echo-bytes",
"echo-configurables",
Expand Down
7 changes: 7 additions & 0 deletions apps/docs/sway/counter-v2/Forc.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[project]
authors = ["Fuel Labs <[email protected]>"]
entry = "main.sw"
license = "Apache-2.0"
name = "counter-v2"

[dependencies]
52 changes: 52 additions & 0 deletions apps/docs/sway/counter-v2/src/main.sw
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// #region proxy-5
contract;

abi Counter {
#[storage(read)]
fn get_count() -> u64;

#[storage(read)]
fn get_increments() -> u64;

#[storage(write, read)]
fn increment_count(amount: u64) -> u64;

#[storage(write, read)]
fn decrement_count(amount: u64) -> u64;
}

storage {
counter: u64 = 0,
increments: u64 = 0,
}

impl Counter for Contract {
#[storage(read)]
fn get_count() -> u64 {
storage.counter.try_read().unwrap_or(0)
}

#[storage(read)]
fn get_increments() -> u64 {
storage.increments.try_read().unwrap_or(0)
}

#[storage(write, read)]
fn increment_count(amount: u64) -> u64 {
let current = storage.counter.try_read().unwrap_or(0);
storage.counter.write(current + amount);

let current_iteration: u64 = storage.increments.try_read().unwrap_or(0);
storage.increments.write(current_iteration + 1);

storage.counter.read()
}

#[storage(write, read)]
fn decrement_count(amount: u64) -> u64 {
let current = storage.counter.try_read().unwrap_or(0);
storage.counter.write(current - amount);
storage.counter.read()
}
}
// #endregion proxy-5
8 changes: 5 additions & 3 deletions apps/docs/sway/counter/src/main.sw
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// #region proxy-1
contract;

abi Counter {
Expand All @@ -18,20 +19,21 @@ storage {
impl Counter for Contract {
#[storage(read)]
fn get_count() -> u64 {
storage.counter.read()
storage.counter.try_read().unwrap_or(0)
}

#[storage(write, read)]
fn increment_count(amount: u64) -> u64 {
let current = storage.counter.read();
let current = storage.counter.try_read().unwrap_or(0);
storage.counter.write(current + amount);
storage.counter.read()
}

#[storage(write, read)]
fn decrement_count(amount: u64) -> u64 {
let current = storage.counter.read();
let current = storage.counter.try_read().unwrap_or(0);
storage.counter.write(current - amount);
storage.counter.read()
}
}
// #endregion proxy-1
46 changes: 46 additions & 0 deletions packages/fuel-gauge/src/recipes.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { hexlify, randomBytes, Src14OwnedProxy, Src14OwnedProxyFactory } from 'fuels';
import { launchTestNode } from 'fuels/test-utils';

/**
* @group node
* @group browser
*/
describe('recipes', () => {
it('deploy and interact with Src14OwnedProxy', async () => {
using launched = await launchTestNode();

const {
wallets: [wallet],
} = launched;

const targetAddress = hexlify(randomBytes(32));
const configurableConstants = {
INITIAL_TARGET: { bits: targetAddress },
INITIAL_OWNER: { Initialized: { Address: { bits: wallet.address.toB256() } } },
};

const proxyFactory = new Src14OwnedProxyFactory(wallet);
const { waitForResult: waitForProxyDeploy } = await proxyFactory.deploy({
configurableConstants,
});
const { contract: proxyContract } = await waitForProxyDeploy();
const { waitForResult: waitForProxyInit } = await proxyContract.functions
.initialize_proxy()
.call();
await waitForProxyInit();
const proxyAddress = proxyContract.id.toB256();

const { waitForResult: waitForFirstTarget } = await proxyContract.functions
.proxy_target()
.call();
const firstTarget = await waitForFirstTarget();
expect(firstTarget.value.bits).toEqual(targetAddress);

const anotherProxy = new Src14OwnedProxy(proxyAddress, wallet);
const { waitForResult: waitForAnotherTarget } = await anotherProxy.functions
.proxy_target()
.call();
const anotherTarget = await waitForAnotherTarget();
expect(anotherTarget.value.bits).toEqual(targetAddress);
});
});
4 changes: 2 additions & 2 deletions packages/fuels/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,7 @@
"dist"
],
"scripts": {
"build": "run-s build:proxy build:package build:browser build:minified",
"build:proxy": "tsx scripts/build-proxy-contract.ts",
"build": "run-s build:package build:browser build:minified",
"build:package": "tsup",
"build:browser": "pnpm vite build",
"build:minified": "pnpm uglifyjs --compress --mangle --output dist/browser.min.mjs -- dist/browser.mjs",
Expand All @@ -79,6 +78,7 @@
"@fuel-ts/transactions": "workspace:*",
"@fuel-ts/utils": "workspace:*",
"@fuel-ts/versions": "workspace:*",
"@fuel-ts/recipes": "workspace:*",
"bundle-require": "^5.0.0",
"chalk": "4",
"chokidar": "^3.6.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { WalletUnlocked } from '@fuel-ts/account';
import { ContractFactory } from '@fuel-ts/contract';
import type { DeployContractOptions } from '@fuel-ts/contract';
import { Contract } from '@fuel-ts/program';
import { Src14OwnedProxy, Src14OwnedProxyFactory } from '@fuel-ts/recipes';
import { existsSync, readFileSync } from 'fs';

import {
Expand All @@ -20,7 +21,6 @@ import { debug, log } from '../../utils/logger';

import { createWallet } from './createWallet';
import { getDeployConfig } from './getDeployConfig';
import { Src14OwnedProxy, Src14OwnedProxyFactory } from './proxy/types';

/**
* Deploys one contract.
Expand Down
2 changes: 0 additions & 2 deletions packages/fuels/src/cli/commands/deploy/proxy/.gitignore

This file was deleted.

Loading