Skip to content

Commit

Permalink
Update Deployments (#56)
Browse files Browse the repository at this point in the history
* Start working on deterministic token deployments

* Simplify deploys using create2 opcode

* Rename token factory

* Format

* Clean up

* Block duplicate messages

* Format

* fix tests

---------

Co-authored-by: Adam Stox <[email protected]>
Jeroen Offerijns and AStox authored Apr 14, 2023
1 parent 6bb2530 commit 5f19c1c
Showing 11 changed files with 157 additions and 37 deletions.
2 changes: 1 addition & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
@@ -21,4 +21,4 @@ fuzz_max_local_rejects = 4096
runs = 10 # The number of calls to make in the invariant tests
depth = 1000 # The number of times to run the invariant tests
call_override = false # Override calls
fail_on_revert = true # Fail the test if the contract reverts
fail_on_revert = false # Fail the test if the contract reverts
6 changes: 3 additions & 3 deletions script/Connector-Axelar.s.sol
Original file line number Diff line number Diff line change
@@ -5,20 +5,20 @@ import {ConnectorAxelarRouter} from "src/routers/axelar/Router.sol";
import {ConnectorGateway} from "src/routers/Gateway.sol";
import {CentrifugeConnector} from "src/Connector.sol";
import {ConnectorEscrow} from "src/Escrow.sol";
import {RestrictedTokenFactory, MemberlistFactory} from "src/token/factory.sol";
import {TrancheTokenFactory, MemberlistFactory} from "src/token/factory.sol";
import "forge-std/Script.sol";

// Script to deploy Connectors with an Axelar router.
contract ConnectorAxelarScript is Script {
// address(0)[0:20] + heccak("Centrifuge")[21:32]
// address(0)[0:20] + keccak("Centrifuge")[21:32]
bytes32 SALT = 0x000000000000000000000000000000000000000075eb27011b69f002dc094d05;

function setUp() public {}

function run() public {
vm.startBroadcast();

address tokenFactory_ = address(new RestrictedTokenFactory{ salt: SALT }());
address tokenFactory_ = address(new TrancheTokenFactory{ salt: SALT }());
address memberlistFactory_ = address(new MemberlistFactory{ salt: SALT }());
address escrow_ = address(new ConnectorEscrow{ salt: SALT }());
CentrifugeConnector connector =
6 changes: 3 additions & 3 deletions script/Connector-XCM.s.sol
Original file line number Diff line number Diff line change
@@ -5,20 +5,20 @@ import {ConnectorXCMRouter} from "src/routers/xcm/Router.sol";
import {ConnectorGateway} from "src/routers/Gateway.sol";
import {CentrifugeConnector} from "src/Connector.sol";
import {ConnectorEscrow} from "src/Escrow.sol";
import {RestrictedTokenFactory, MemberlistFactory} from "src/token/factory.sol";
import {TrancheTokenFactory, MemberlistFactory} from "src/token/factory.sol";
import "forge-std/Script.sol";

// Script to deploy Connectors with an XCM router.
contract ConnectorXCMScript is Script {
// address(0)[0:20] + heccak("Centrifuge")[21:32]
// address(0)[0:20] + keccak("Centrifuge")[21:32]
bytes32 SALT = 0x000000000000000000000000000000000000000075eb27011b69f002dc094d05;

function setUp() public {}

function run() public {
vm.startBroadcast();

address tokenFactory_ = address(new RestrictedTokenFactory{ salt: SALT }());
address tokenFactory_ = address(new TrancheTokenFactory{ salt: SALT }());
address memberlistFactory_ = address(new MemberlistFactory{ salt: SALT }());
address escrow_ = address(new ConnectorEscrow{ salt: SALT }());
CentrifugeConnector connector =
12 changes: 8 additions & 4 deletions src/Connector.sol
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@
pragma solidity ^0.8.18;
pragma abicoder v2;

import {RestrictedTokenFactoryLike, MemberlistFactoryLike} from "./token/factory.sol";
import {TrancheTokenFactoryLike, MemberlistFactoryLike} from "./token/factory.sol";
import {RestrictedTokenLike, ERC20Like} from "./token/restricted.sol";
import {MemberlistLike} from "./token/memberlist.sol";

@@ -50,7 +50,7 @@ contract CentrifugeConnector {
GatewayLike public gateway;
EscrowLike public escrow;

RestrictedTokenFactoryLike public immutable tokenFactory;
TrancheTokenFactoryLike public immutable tokenFactory;
MemberlistFactoryLike public immutable memberlistFactory;

// --- Events ---
@@ -63,7 +63,7 @@ contract CentrifugeConnector {

constructor(address escrow_, address tokenFactory_, address memberlistFactory_) {
escrow = EscrowLike(escrow_);
tokenFactory = RestrictedTokenFactoryLike(tokenFactory_);
tokenFactory = TrancheTokenFactoryLike(tokenFactory_);
memberlistFactory = MemberlistFactoryLike(memberlistFactory_);

wards[msg.sender] = 1;
@@ -184,6 +184,7 @@ contract CentrifugeConnector {
// todo(nuno): store currency and decimals
function addPool(uint64 poolId, uint128 currency, uint8 decimals) public onlyGateway {
Pool storage pool = pools[poolId];
require(pool.createdAt == 0, "CentrifugeConnector/pool-already-added");
pool.poolId = poolId;
pool.createdAt = block.timestamp;
emit PoolAdded(poolId);
@@ -200,6 +201,7 @@ contract CentrifugeConnector {
require(pool.createdAt > 0, "CentrifugeConnector/invalid-pool");

Tranche storage tranche = tranches[poolId][trancheId];
require(tranche.lastPriceUpdate == 0, "CentrifugeConnector/tranche-already-added");
tranche.latestPrice = price;
tranche.lastPriceUpdate = block.timestamp;
tranche.tokenName = tokenName;
@@ -211,10 +213,12 @@ contract CentrifugeConnector {
function deployTranche(uint64 poolId, bytes16 trancheId) public {
Tranche storage tranche = tranches[poolId][trancheId];
require(tranche.lastPriceUpdate > 0, "CentrifugeConnector/invalid-pool-or-tranche");
require(tranche.token == address(0), "CentrifugeConnector/tranche-already-deployed");

// TODO: use actual decimals
uint8 decimals = 18;
address token = tokenFactory.newRestrictedToken(tranche.tokenName, tranche.tokenSymbol, decimals);
address token =
tokenFactory.newTrancheToken(poolId, trancheId, tranche.tokenName, tranche.tokenSymbol, decimals);
tranche.token = token;

address memberlist = memberlistFactory.newMemberlist();
12 changes: 5 additions & 7 deletions src/token/erc20.sol
Original file line number Diff line number Diff line change
@@ -14,7 +14,7 @@ contract ERC20 {
string public name;
string public symbol;
string public constant version = "3";
uint8 public decimals;
uint8 public immutable decimals;
uint256 public totalSupply;

mapping(address => uint256) public balanceOf;
@@ -34,9 +34,7 @@ contract ERC20 {
event Approval(address indexed owner, address indexed spender, uint256 value);
event Transfer(address indexed from, address indexed to, uint256 value);

constructor(string memory name_, string memory symbol_, uint8 decimals_) {
name = name_;
symbol = symbol_;
constructor(uint8 decimals_) {
decimals = decimals_;

wards[msg.sender] = 1;
@@ -63,7 +61,7 @@ contract ERC20 {
}

modifier auth() {
require(wards[msg.sender] == 1, "not-authorized");
require(wards[msg.sender] == 1, "ERC20/not-authorized");
_;
}

@@ -79,8 +77,8 @@ contract ERC20 {
}

function file(bytes32 what, string memory data) external auth {
if (what == "name") name = name;
else if (what == "symbol") symbol = symbol;
if (what == "name") name = data;
else if (what == "symbol") symbol = data;
else revert("ERC20/file-unrecognized-param");
emit File(what, data);
}
28 changes: 23 additions & 5 deletions src/token/factory.sol
Original file line number Diff line number Diff line change
@@ -4,13 +4,31 @@ pragma solidity ^0.8.18;
import {RestrictedToken} from "./restricted.sol";
import {Memberlist} from "./memberlist.sol";

interface RestrictedTokenFactoryLike {
function newRestrictedToken(string calldata, string calldata, uint8) external returns (address);
interface ImmutableCreate2Factory {
function safeCreate2(bytes32 salt, bytes calldata initCode) external payable returns (address deploymentAddress);
}

contract RestrictedTokenFactory {
function newRestrictedToken(string memory name, string memory symbol, uint8 decimals) public returns (address) {
RestrictedToken token = new RestrictedToken(name, symbol, decimals);
interface TrancheTokenFactoryLike {
function newTrancheToken(uint64, bytes16, string calldata, string calldata, uint8) external returns (address);
}

contract TrancheTokenFactory {
function newTrancheToken(uint64 poolId, bytes16 trancheId, string memory name, string memory symbol, uint8 decimals)
public
returns (address)
{
// Salt is hash(poolId + trancheId), to deploy copies of the restricted token contract
// on multiple chains with the same address for the same tranche
bytes32 salt = keccak256(abi.encodePacked(poolId, trancheId));

RestrictedToken token = new RestrictedToken{salt: salt}(decimals);

// Name and symbol are not passed on constructor, such that if the same tranche is deployed
// on another chain with a different name (it might have changed in between deployments),
// then the address remains deterministic.
token.file("name", name);
token.file("symbol", symbol);

token.rely(msg.sender);
token.deny(address(this));
return address(token);
2 changes: 1 addition & 1 deletion src/token/restricted.sol
Original file line number Diff line number Diff line change
@@ -32,7 +32,7 @@ contract RestrictedToken is ERC20 {
// --- Events ---
event File(bytes32 indexed what, address data);

constructor(string memory name_, string memory symbol_, uint8 decimals_) ERC20(name_, symbol_, decimals_) {}
constructor(uint8 decimals_) ERC20(decimals_) {}

modifier checkMember(address user) {
memberlist.member(user);
68 changes: 60 additions & 8 deletions test/Connector.t.sol
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@ pragma abicoder v2;
import {CentrifugeConnector} from "src/Connector.sol";
import {ConnectorGateway} from "src/routers/Gateway.sol";
import {ConnectorEscrow} from "src/Escrow.sol";
import {RestrictedTokenFactory, MemberlistFactory} from "src/token/factory.sol";
import {TrancheTokenFactory, MemberlistFactory} from "src/token/factory.sol";
import {RestrictedTokenLike} from "src/token/restricted.sol";
import {MemberlistLike, Memberlist} from "src/token/memberlist.sol";
import {MockHomeConnector} from "./mock/MockHomeConnector.sol";
@@ -23,7 +23,7 @@ contract ConnectorTest is Test {
function setUp() public {
vm.chainId(1);
address escrow_ = address(new ConnectorEscrow());
address tokenFactory_ = address(new RestrictedTokenFactory());
address tokenFactory_ = address(new TrancheTokenFactory());
address memberlistFactory_ = address(new MemberlistFactory());

bridgedConnector = new CentrifugeConnector(escrow_, tokenFactory_, memberlistFactory_);
@@ -42,6 +42,13 @@ contract ConnectorTest is Test {
assertEq(uint256(actualPoolId), uint256(poolId));
}

function testAddingPoolMultipleTimesFails(uint64 poolId, uint128 currency, uint8 decimals) public {
connector.addPool(poolId, currency, decimals);

vm.expectRevert(bytes("CentrifugeConnector/pool-already-added"));
connector.addPool(poolId, currency, decimals);
}

function testAddingPoolAsNonRouterFails(uint64 poolId, uint128 currency, uint8 decimals) public {
vm.expectRevert(bytes("CentrifugeConnector/not-the-gateway"));
bridgedConnector.addPool(poolId, currency, decimals);
@@ -80,6 +87,22 @@ contract ConnectorTest is Test {
assertEq(token.symbol(), bytes32ToString(stringToBytes32(tokenSymbol)));
}

function testAddingTrancheMultipleTimesFails(
uint64 poolId,
uint128 currency,
uint8 decimals,
string memory tokenName,
string memory tokenSymbol,
bytes16 trancheId,
uint128 price
) public {
connector.addPool(poolId, currency, decimals);
connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, price);

vm.expectRevert(bytes("CentrifugeConnector/tranche-already-added"));
connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, price);
}

function testAddingMultipleTranchesWorks(
uint64 poolId,
uint128 currency,
@@ -89,6 +112,8 @@ contract ConnectorTest is Test {
string memory tokenSymbol,
uint128 price
) public {
vm.assume(trancheIds.length > 0 && trancheIds.length < 5);
vm.assume(!hasDuplicates(trancheIds));
connector.addPool(poolId, currency, decimals);

for (uint256 i = 0; i < trancheIds.length; i++) {
@@ -125,6 +150,23 @@ contract ConnectorTest is Test {
connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, price);
}

function testDeployingTrancheMultipleTimesFails(
uint64 poolId,
uint128 currency,
uint8 decimals,
string memory tokenName,
string memory tokenSymbol,
bytes16 trancheId,
uint128 price
) public {
connector.addPool(poolId, currency, decimals);
connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, price);
bridgedConnector.deployTranche(poolId, trancheId);

vm.expectRevert(bytes("CentrifugeConnector/tranche-already-deployed"));
bridgedConnector.deployTranche(poolId, trancheId);
}

function testDeployingWrongTrancheFails(
uint64 poolId,
uint128 currency,
@@ -222,10 +264,9 @@ contract ConnectorTest is Test {
uint64 validUntil
) public {
vm.assume(validUntil > block.timestamp);
bridgedConnector.file("gateway", address(this));
bridgedConnector.addPool(poolId, currency, decimals);
connector.addPool(poolId, currency, decimals);
vm.expectRevert(bytes("CentrifugeConnector/invalid-pool-or-tranche"));
bridgedConnector.updateMember(poolId, trancheId, user, validUntil);
connector.updateMember(poolId, trancheId, user, validUntil);
}

function testUpdatingTokenPriceWorks(
@@ -270,10 +311,9 @@ contract ConnectorTest is Test {
bytes16 trancheId,
uint128 price
) public {
bridgedConnector.file("gateway", address(this));
bridgedConnector.addPool(poolId, currency, decimals);
connector.addPool(poolId, currency, decimals);
vm.expectRevert(bytes("CentrifugeConnector/invalid-pool-or-tranche"));
bridgedConnector.updateTokenPrice(poolId, trancheId, price);
connector.updateTokenPrice(poolId, trancheId, price);
}

// Test transferring `amount` to the address(this)'s account (Centrifuge Chain -> EVM like) and then try
@@ -466,4 +506,16 @@ contract ConnectorTest is Test {
}
return fc;
}

function hasDuplicates(bytes16[] calldata array) internal pure returns (bool) {
uint256 length = array.length;
for (uint256 i = 0; i < length; i++) {
for (uint256 j = i + 1; j < length; j++) {
if (array[i] == array[j]) {
return true;
}
}
}
return false;
}
}
4 changes: 2 additions & 2 deletions test/Invariants.t.sol
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@ import {ConnectorEscrow} from "src/Escrow.sol";
import {MockHomeConnector} from "./mock/MockHomeConnector.sol";
import "./mock/MockXcmRouter.sol";
import {ConnectorGateway} from "src/routers/Gateway.sol";
import {RestrictedTokenFactory, MemberlistFactory} from "src/token/factory.sol";
import {TrancheTokenFactory, MemberlistFactory} from "src/token/factory.sol";
import {InvariantPoolManager} from "./accounts/PoolManager.sol";
import "forge-std/Test.sol";
import "../src/Connector.sol";
@@ -24,7 +24,7 @@ contract ConnectorInvariants is Test {

function setUp() public {
address escrow_ = address(new ConnectorEscrow());
address tokenFactory_ = address(new RestrictedTokenFactory());
address tokenFactory_ = address(new TrancheTokenFactory());
address memberlistFactory_ = address(new MemberlistFactory());
bridgedConnector = new CentrifugeConnector(escrow_, tokenFactory_, memberlistFactory_);
mockXcmRouter = new MockXcmRouter(address(bridgedConnector));
48 changes: 48 additions & 0 deletions test/token/factory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.18;
pragma abicoder v2;

import {TrancheTokenFactory, MemberlistFactory} from "src/token/factory.sol";
import "forge-std/Test.sol";

contract FactoryTest is Test {
// address(0)[0:20] + keccak("Centrifuge")[21:32]
bytes32 SALT = 0x000000000000000000000000000000000000000075eb27011b69f002dc094d05;

bool isFirstRun = false;
address tokenFactoryAddress;
address tokenAddress;

function setUp() public {}

// function testTokenAddressShouldBeDeterministic(
// address sender,
// uint64 chainId,
// string memory name,
// string memory symbol
// ) public {
// TrancheTokenFactory tokenFactory = new TrancheTokenFactory{ salt: SALT }();

// if (isFirstRun) {
// tokenFactoryAddress = address(tokenFactory);
// } else {
// assertEq(address(tokenFactory), tokenFactoryAddress);
// }

// vm.prank(sender);
// vm.chainId(uint256(chainId));

// uint64 fixedPoolId = 1;
// bytes16 fixedTrancheId = "1";
// uint8 fixedDecimals = 18;

// address token = tokenFactory.newTrancheToken(fixedPoolId, fixedTrancheId, name, symbol, fixedDecimals);

// if (isFirstRun) {
// tokenAddress = address(tokenFactory);
// isFirstRun = false;
// } else {
// assertEq(token, tokenAddress);
// }
// }
}
6 changes: 3 additions & 3 deletions test/token/restricted.sol
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@
pragma solidity ^0.8.18;
pragma abicoder v2;

import {RestrictedTokenFactory, MemberlistFactory} from "src/token/factory.sol";
import {TrancheTokenFactory, MemberlistFactory} from "src/token/factory.sol";
import {RestrictedTokenLike} from "src/token/restricted.sol";
import {MemberlistLike, Memberlist} from "src/token/memberlist.sol";
import "forge-std/Test.sol";
@@ -16,10 +16,10 @@ contract RestrictedTokenTest is Test {
MemberlistLike memberlist;

function setUp() public {
RestrictedTokenFactory tokenFactory = new RestrictedTokenFactory();
TrancheTokenFactory tokenFactory = new TrancheTokenFactory();
MemberlistFactory memberlistFactory = new MemberlistFactory();

token = RestrictedTokenLike(tokenFactory.newRestrictedToken("Some Token", "ST", 18));
token = RestrictedTokenLike(tokenFactory.newTrancheToken(1, "1", "Some Token", "ST", 18));

memberlist = MemberlistLike(memberlistFactory.newMemberlist());
token.file("memberlist", address(memberlist));

0 comments on commit 5f19c1c

Please sign in to comment.