Skip to content

Commit 2bb404a

Browse files
dbeal-ethmjlescano
andauthored
create associated systems module (#1043)
* create associated systems module * created `AssociatedSystemsModule` which is sort of like a modernized version of the addressresolver for v3. You specify an implementation address, (which can be a *router* deployed by the deployer with cannon) and then it will deploy a proxy to officially connect it to the system. Supports doing the upgrades in the same call. also supports adding unmanaged contracts (ex. when we deploy to prod, using the old SNX contract) * change deployer ability to *not* deploy proxies. This should be done with cannon now * change deployer to take in `modules` argument (positional), which has the ability to specify which modules to deploy. This is used by cannon to build subsets of the module directory for the 4 different systems specified above. * add `TokenModule` and `NftModule` to `core-modules` which implements everything needed for a basic token as the title implies * remove checks for initialization modules (though not the initialization module itself), since initialization will be a much more customized process going forward and will be done by just implementing "isInitialized" for your module. * lint * add skipProxy option and remove unnecessary task name * better logging for cli mismatch errors * oops * remove unnecessary log * fix deployer * fix pkglock * fix deployer merge issues * lint fix * update pkglock * update module tests * add tests for associated systems manager and TokenModule and NftModule * lint * figured out why owner calls werent working turns out the deployment was being overwritten with same modules. to solve, deploy all systems to a new instance thanks @leo * fix from review * remove rogue file * fix cli-runner lint warnings * remove proxyContract configuration from deployments util Co-authored-by: Matías <[email protected]>
1 parent 11967f7 commit 2bb404a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+17542
-12014
lines changed

package-lock.json

+16,451-11,744
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/cli/test/integration/helpers/cli-runner.js

+12-10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const assert = require('assert/strict');
2-
const chalk = require('chalk');
32
const { spawn } = require('child_process');
3+
const { setTimeout } = require('timers/promises');
4+
const chalk = require('chalk');
45

56
// Set these to false on CI
67
const SHOW_CLI_OUTPUT = false;
@@ -12,7 +13,7 @@ const INTERACT_DELAY = 1000;
1213
class CliRunner {
1314
constructor() {}
1415

15-
async start() {
16+
start() {
1617
this.errors = [];
1718
this.buffer = '';
1819

@@ -38,27 +39,24 @@ class CliRunner {
3839

3940
this.buffer += str;
4041
});
42+
4143
this.cliProcess.stderr.on('data', (data) => {
4244
console.error(data.toString());
4345

4446
this.errors.push(data.toString());
4547
});
4648

47-
return new Promise((resolve) => {
48-
setTimeout(resolve, START_DELAY);
49-
});
49+
return setTimeout(START_DELAY);
5050
}
5151

52-
async interact(cmd) {
52+
interact(cmd) {
5353
if (SHOW_CLI_INTERACTIONS) {
5454
console.log(`CLI input: ${cmd}`);
5555
}
5656

5757
this.cliProcess.stdin.write(cmd);
5858

59-
return new Promise((resolve) => {
60-
setTimeout(resolve, INTERACT_DELAY);
61-
});
59+
return setTimeout(INTERACT_DELAY);
6260
}
6361

6462
clear() {
@@ -68,7 +66,11 @@ class CliRunner {
6866
printed(txt) {
6967
const includes = this.buffer.includes(txt);
7068
if (!includes) {
71-
console.error(`CLI output was expected to include "${chalk.red(txt)}", but it does not.`);
69+
console.error(
70+
`CLI output should contain "${chalk.white(txt)}", but it was "${chalk.red(
71+
this.buffer.toString()
72+
)}".`
73+
);
7274
}
7375

7476
assert.ok(includes);

packages/core-js/utils/ethers/events.js

+10
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,16 @@
99
function findEvent({ receipt, eventName, contract = undefined }) {
1010
let events = receipt.events;
1111

12+
if (!receipt) {
13+
throw new Error(`receipt when searching for event ${eventName} is null/undefined.`);
14+
}
15+
16+
if (!receipt.logs) {
17+
throw new Error(
18+
`no logs found when searching for event ${eventName}. Did you actually pass a transaction receipt into findEvent?`
19+
);
20+
}
21+
1222
if (!events || (events.some((e) => e.event === undefined) && contract)) {
1323
events = parseLogs({ contract, logs: receipt.logs });
1424
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
//SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.0;
3+
4+
/// @title Module for managing snxUSD token as a Satellite
5+
interface IAssociatedSystemsConsumerModule {
6+
function getToken(bytes32 id) external view returns (address);
7+
8+
function getNft(bytes32 id) external view returns (address);
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.0;
3+
4+
/// @title Allows for the registration and tracking of auxillery contracts which also follow the proxy architecture
5+
interface IAssociatedSystemsModule {
6+
/// @notice create or initialize a new token
7+
function initOrUpgradeToken(
8+
bytes32 id,
9+
string memory name,
10+
string memory symbol,
11+
uint8 decimals,
12+
address impl
13+
) external;
14+
15+
function initOrUpgradeNft(
16+
bytes32 id,
17+
string memory name,
18+
string memory symbol,
19+
string memory uri,
20+
address impl
21+
) external;
22+
23+
function registerUnmanagedSystem(bytes32 id, address endpoint) external;
24+
25+
function getAssociatedSystem(bytes32 id) external view returns (address proxy, bytes32 kind);
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.0;
3+
4+
import "@synthetixio/core-contracts/contracts/interfaces/IERC721.sol";
5+
6+
/// @title NFT token identifying an Account
7+
interface INftModule is IERC721 {
8+
/// @notice returns if `initialize` has been called by the owner
9+
function isInitialized() external returns (bool);
10+
11+
/// @notice allows owner to initialize the token after attaching a proxy
12+
function initialize(
13+
string memory tokenName,
14+
string memory tokenSymbol,
15+
string memory uri
16+
) external;
17+
18+
/// @notice mints a new token (NFT) with the "requestedAccountId" id owned by "owner". It can ol=nly be called by the system
19+
function mint(address owner, uint requestedAccountId) external;
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.0;
3+
4+
import "@synthetixio/core-contracts/contracts/interfaces/IERC20.sol";
5+
6+
/// @title ERC20 token for snxUSD
7+
interface ITokenModule is IERC20 {
8+
/// @notice returns if `initialize` has been called by the owner
9+
function isInitialized() external returns (bool);
10+
11+
/// @notice allows owner to initialize the token after attaching a proxy
12+
function initialize(
13+
string memory tokenName,
14+
string memory tokenSymbol,
15+
uint8 tokenDecimals
16+
) external;
17+
18+
/// @notice mints token amount to "to" address
19+
function mint(address to, uint amount) external;
20+
21+
/// @notice burns token amount from "to" address
22+
function burn(address to, uint amount) external;
23+
24+
/// @notice sets token amount allowance to spender by "from" address
25+
function setAllowance(
26+
address from,
27+
address spender,
28+
uint amount
29+
) external;
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.0;
3+
4+
import "@synthetixio/core-contracts/contracts/errors/InitError.sol";
5+
import "../storage/AssociatedSystemsStorage.sol";
6+
7+
import "../interfaces/ITokenModule.sol";
8+
import "../interfaces/INftModule.sol";
9+
10+
contract AssociatedSystemsMixin is AssociatedSystemsStorage {
11+
error MismatchAssociatedSystemKind(bytes32 expected, bytes32 actual);
12+
13+
bytes32 internal constant _KIND_ERC20 = "erc20";
14+
bytes32 internal constant _KIND_ERC721 = "erc721";
15+
bytes32 internal constant _KIND_UNMANAGED = "unmanaged";
16+
17+
function _getToken(bytes32 id) internal view returns (ITokenModule) {
18+
_requireKind(id, _KIND_ERC20);
19+
return ITokenModule(_associatedSystemsStore().satellites[id].proxy);
20+
}
21+
22+
function _getNft(bytes32 id) internal view returns (INftModule) {
23+
_requireKind(id, _KIND_ERC721);
24+
return INftModule(_associatedSystemsStore().satellites[id].proxy);
25+
}
26+
27+
function _requireKind(bytes32 id, bytes32 kind) internal view {
28+
bytes32 actualKind = _associatedSystemsStore().satellites[id].kind;
29+
30+
if (actualKind != kind && actualKind != _KIND_UNMANAGED) {
31+
revert MismatchAssociatedSystemKind(kind, actualKind);
32+
}
33+
}
34+
35+
modifier onlyIfAssociated(bytes32 id) {
36+
if (address(_associatedSystemsStore().satellites[id].proxy) == address(0)) {
37+
revert InitError.NotInitialized();
38+
}
39+
40+
_;
41+
}
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
//SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.0;
3+
4+
import "@synthetixio/core-contracts/contracts/ownership/OwnableMixin.sol";
5+
import "@synthetixio/core-contracts/contracts/proxy/UUPSProxy.sol";
6+
import "../interfaces/IAssociatedSystemsModule.sol";
7+
import "../mixins/AssociatedSystemsMixin.sol";
8+
9+
import "@synthetixio/core-contracts/contracts/interfaces/IUUPSImplementation.sol";
10+
import "../interfaces/IOwnerModule.sol";
11+
import "../interfaces/ITokenModule.sol";
12+
import "../interfaces/INftModule.sol";
13+
14+
contract AssociatedSystemsModule is IAssociatedSystemsModule, OwnableMixin, AssociatedSystemsMixin {
15+
function initOrUpgradeToken(
16+
bytes32 id,
17+
string memory name,
18+
string memory symbol,
19+
uint8 decimals,
20+
address impl
21+
) external override onlyOwner {
22+
AssociatedSystemsStore storage store = _associatedSystemsStore();
23+
24+
if (store.satellites[id].proxy != address(0)) {
25+
_requireKind(id, _KIND_ERC20);
26+
27+
store.satellites[id].impl = impl;
28+
29+
address proxy = store.satellites[id].proxy;
30+
31+
// tell the associated proxy to upgrade to the new implementation
32+
IUUPSImplementation(proxy).upgradeTo(impl);
33+
34+
_setAssociatedSystem(id, _KIND_ERC20, proxy, impl);
35+
} else {
36+
// create a new proxy and own it
37+
address proxy = address(new UUPSProxy(impl));
38+
39+
IOwnerModule(proxy).initializeOwnerModule(address(this));
40+
ITokenModule(proxy).initialize(name, symbol, decimals);
41+
42+
_setAssociatedSystem(id, _KIND_ERC20, proxy, impl);
43+
}
44+
}
45+
46+
function initOrUpgradeNft(
47+
bytes32 id,
48+
string memory name,
49+
string memory symbol,
50+
string memory uri,
51+
address impl
52+
) external override onlyOwner {
53+
AssociatedSystemsStore storage store = _associatedSystemsStore();
54+
55+
if (store.satellites[id].proxy != address(0)) {
56+
_requireKind(id, _KIND_ERC721);
57+
58+
address proxy = store.satellites[id].proxy;
59+
60+
// tell the associated proxy to upgrade to the new implementation
61+
IUUPSImplementation(proxy).upgradeTo(impl);
62+
63+
_setAssociatedSystem(id, _KIND_ERC721, proxy, impl);
64+
} else {
65+
// create a new proxy and own it
66+
address proxy = address(new UUPSProxy(impl));
67+
68+
IOwnerModule(proxy).initializeOwnerModule(address(this));
69+
INftModule(proxy).initialize(name, symbol, uri);
70+
71+
_setAssociatedSystem(id, _KIND_ERC721, proxy, impl);
72+
}
73+
}
74+
75+
/**
76+
* sets a token implementation without the corresponding upgrade functionality
77+
* useful for adaptation of ex. old SNX token. The connected system does not need to be
78+
*
79+
* *NOTE:* the contract you are connecting should still be owned by your dao. The
80+
* system is not expected to be able to do upgrades for you.
81+
*/
82+
function registerUnmanagedSystem(bytes32 id, address endpoint) external override onlyOwner {
83+
// empty string require kind will make sure the system is either unregistered or already unmanaged
84+
_requireKind(id, "");
85+
86+
_setAssociatedSystem(id, _KIND_UNMANAGED, endpoint, endpoint);
87+
}
88+
89+
function _setAssociatedSystem(
90+
bytes32 id,
91+
bytes32 kind,
92+
address proxy,
93+
address impl
94+
) internal {
95+
_associatedSystemsStore().satellites[id] = AssociatedSystem(proxy, impl, kind);
96+
emit AssociatedSystemSet(kind, id, proxy, impl);
97+
}
98+
99+
function getAssociatedSystem(bytes32 id) external view override returns (address proxy, bytes32 kind) {
100+
proxy = _associatedSystemsStore().satellites[id].proxy;
101+
kind = _associatedSystemsStore().satellites[id].kind;
102+
}
103+
104+
event AssociatedSystemSet(bytes32 indexed kind, bytes32 indexed id, address proxy, address impl);
105+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
//SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.0;
3+
4+
import "@synthetixio/core-contracts/contracts/token/ERC721.sol";
5+
import "@synthetixio/core-contracts/contracts/utils/AddressUtil.sol";
6+
import "@synthetixio/core-contracts/contracts/initializable/InitializableMixin.sol";
7+
import "@synthetixio/core-contracts/contracts/ownership/OwnableMixin.sol";
8+
import "@synthetixio/core-contracts/contracts/errors/AddressError.sol";
9+
10+
import "../storage/NftStorage.sol";
11+
12+
import "../interfaces/INftModule.sol";
13+
14+
contract NftModule is INftModule, ERC721, NftStorage, InitializableMixin, OwnableMixin {
15+
event Mint(address owner, uint nftId);
16+
17+
// ---------------------------------------
18+
// Chores
19+
// ---------------------------------------
20+
function _isInitialized() internal view override returns (bool) {
21+
return _nftStore().initialized;
22+
}
23+
24+
function isInitialized() external view returns (bool) {
25+
return _isInitialized();
26+
}
27+
28+
function initialize(
29+
string memory tokenName,
30+
string memory tokenSymbol,
31+
string memory uri
32+
) public onlyOwner {
33+
_initialize(tokenName, tokenSymbol, uri);
34+
NftStore storage store = _nftStore();
35+
36+
store.initialized = true;
37+
}
38+
39+
// ---------------------------------------
40+
// Mint/Transfer
41+
// ---------------------------------------
42+
function mint(address owner, uint256 nftId) external override onlyOwner {
43+
_mint(owner, nftId);
44+
45+
emit Mint(owner, nftId);
46+
}
47+
}

0 commit comments

Comments
 (0)