├── .prettierrc.json ├── .gitignore ├── docs └── how_it_all_works.jpeg ├── hardhat.config.js ├── .solhint.json ├── package.json ├── README.md ├── scripts ├── deploySAT.js ├── ExportTokenSaleData.js ├── deployMockAll.js └── deployAll.js ├── contracts ├── VaultTmp.sol ├── StakingWarmup.sol ├── StakingHelper.sol ├── StandardBondingCalculator.sol ├── mock │ ├── DAI.sol │ ├── MockUDSC.sol │ └── MockSAT.sol ├── AdminUpgradeabilityProxy.sol ├── ConsensusPool.sol ├── StakingDistributor.sol └── SAT.sol └── test ├── utils.js ├── SATTimelock-Unit.test.waffle.chai.ethers.js ├── TokenSale-Unit.test.waffle.chai.ethers.js ├── SAT-Unit.test.waffle.chai.ethers.js └── ConsensusPool-Unit.test.waffle.chai.ethers.js /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | artifacts/ 3 | cache/ 4 | .idea/ 5 | -------------------------------------------------------------------------------- /docs/how_it_all_works.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synassets/contracts/HEAD/docs/how_it_all_works.jpeg -------------------------------------------------------------------------------- /hardhat.config.js: -------------------------------------------------------------------------------- 1 | require("@nomiclabs/hardhat-waffle"); 2 | 3 | module.exports = { 4 | solidity: "0.7.5", 5 | }; 6 | -------------------------------------------------------------------------------- /.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:default", 3 | "plugins": ["prettier"], 4 | "rules": { 5 | "prettier/prettier": "error" 6 | } 7 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "synassets-contracts", 3 | "version": "1.0.0", 4 | "description": "Smart Contracts for Synassets", 5 | "directories": { 6 | "test": "test" 7 | }, 8 | "dependencies": { 9 | "@openzeppelin/contracts": "^3.4.0" 10 | }, 11 | "devDependencies": { 12 | "@nomiclabs/hardhat-ethers": "^2.0.2", 13 | "@nomiclabs/hardhat-waffle": "^2.0.1", 14 | "@openzeppelin/test-helpers": "^0.5.12", 15 | "chai": "^4.3.4", 16 | "ethereum-waffle": "^3.4.0", 17 | "ethers": "^5.4.1", 18 | "hardhat": "^2.5.0", 19 | "prettier": "^2.3.2", 20 | "prettier-plugin-solidity": "^1.0.0-beta.17", 21 | "solhint": "^3.3.6", 22 | "solhint-plugin-prettier": "0.0.5" 23 | }, 24 | "scripts": { 25 | "test": "npx hardhat test", 26 | "compile": "npx hardhat compile", 27 | "clean": "npx hardhat clean", 28 | "start": "npx hardhat run scripts/deployAll.js", 29 | "lint:sol": "solhint -f table contracts/**/*.sol" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Smart Contracts 2 | 3 | ## 🔧 Setting up local development 4 | 5 | Requirements: 6 | - [Node v14](https://nodejs.org/download/release/latest-v14.x/) 7 | - [Git](https://git-scm.com/downloads) 8 | 9 | 10 | Local Setup Steps: 11 | 1. ``git clone https://github.com/synassets/contracts.git `` 12 | 1. Install dependencies: `npm install` 13 | - Installs [Hardhat](https://hardhat.org/getting-started/) and [OpenZepplin](https://docs.openzeppelin.com/contracts/4.x/) dependencies 14 | 1. Compile Solidity: ``npm run compile`` 15 | 16 | ## 🤔 token allocation 17 | 18 | ![High Level Contract Interactions](./docs/how_it_all_works.jpeg) 19 | 20 | ## Contract addresses 21 | 22 | ### Mainnet 23 | 24 | Network: `Polygon` 25 | 26 | - TestSAT: `0x8d727C3D99892b285dD7F0D7268E6cE850532E47` 27 | - TestUSDC: `0x70b04a9AbC8D0D2088a3D0895aD3a6363859592f` 28 | - TokenSale-OG: `0xB1601bF2D7FE00d24ccb4037F5D87b7664b19c7e` 29 | - TokenSale-PUB: `0xdCde2c8CC76f9C40f439e294843D9c7DaB7a2A5e` 30 | - Timelock: `0xd4d8FfaC785d14e2e34afFe92193202c3543c0d8` 31 | -------------------------------------------------------------------------------- /scripts/deploySAT.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require("hardhat"); 2 | 3 | async function main() { 4 | const [logicOwner, proxyOwner] = await ethers.getSigners(); 5 | console.log('logicOwner: ' + logicOwner.address); 6 | console.log('proxyOwner: ' + proxyOwner.address); 7 | 8 | const SAT = await ethers.getContractFactory('SATERC20Token'); 9 | const SATTIMELOCK = await ethers.getContractFactory('SATTimelock'); 10 | const PROXY = await ethers.getContractFactory('AdminUpgradeabilityProxy'); 11 | 12 | const logicSAT = await SAT.connect(proxyOwner).deploy(); 13 | await logicSAT.deployed(); 14 | let proxySAT = await PROXY.connect(proxyOwner).deploy(proxyOwner.address, logicSAT.address, '0x'); 15 | await proxySAT.deployed(); 16 | proxySAT = await SAT.attach(proxySAT.address); 17 | await proxySAT.connect(logicOwner).__SATERC20Token_initialize(); 18 | 19 | const logicSATTIMELOCK = await SATTIMELOCK.connect(proxyOwner).deploy(); 20 | await logicSATTIMELOCK.deployed(); 21 | 22 | let proxySATTIMELOCK = await PROXY.connect(proxyOwner).deploy(proxyOwner.address, logicSATTIMELOCK.address, '0x'); 23 | await proxySATTIMELOCK.deployed(); 24 | proxySATTIMELOCK = await SATTIMELOCK.attach(proxySATTIMELOCK.address); 25 | await proxySATTIMELOCK.connect(logicOwner).__SATTimelock_initialize(proxySAT.address, logicOwner.address, 60*24*60*60); 26 | 27 | await proxySAT.connect(logicOwner).pauseTransfer(); 28 | await proxySAT.connect(logicOwner).setFeeAddress(proxySATTIMELOCK.address); 29 | 30 | console.log('logicSAT address: ' + logicSAT.address); 31 | console.log('logicSATTIMELOCK address: ' + logicSATTIMELOCK.address); 32 | 33 | console.log('proxySAT address: ' + proxySAT.address); 34 | console.log('proxySATTIMELOCK address: ' + proxySATTIMELOCK.address); 35 | } 36 | 37 | main() 38 | .then(() => process.exit()) 39 | .catch(error => { 40 | console.error(error); 41 | process.exit(1); 42 | }) 43 | -------------------------------------------------------------------------------- /contracts/VaultTmp.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.7.5; 3 | 4 | interface IERC20 { 5 | /** 6 | * @dev Returns the amount of tokens in existence. 7 | */ 8 | function totalSupply() external view returns (uint256); 9 | 10 | /** 11 | * @dev Returns the amount of tokens owned by `account`. 12 | */ 13 | function balanceOf(address account) external view returns (uint256); 14 | 15 | /** 16 | * @dev Moves `amount` tokens from the caller's account to `recipient`. 17 | * 18 | * Returns a boolean value indicating whether the operation succeeded. 19 | * 20 | * Emits a {Transfer} event. 21 | */ 22 | function transfer(address recipient, uint256 amount) external returns (bool); 23 | 24 | /** 25 | * @dev Returns the remaining number of tokens that `spender` will be 26 | * allowed to spend on behalf of `owner` through {transferFrom}. This is 27 | * zero by default. 28 | * 29 | * This value changes when {approve} or {transferFrom} are called. 30 | */ 31 | function allowance(address owner, address spender) external view returns (uint256); 32 | 33 | /** 34 | * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. 35 | * 36 | * Returns a boolean value indicating whether the operation succeeded. 37 | * 38 | * IMPORTANT: Beware that changing an allowance with this method brings the risk 39 | * that someone may use both the old and the new allowance by unfortunate 40 | * transaction ordering. One possible solution to mitigate this race 41 | * condition is to first reduce the spender's allowance to 0 and set the 42 | * desired value afterwards: 43 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 44 | * 45 | * Emits an {Approval} event. 46 | */ 47 | function approve(address spender, uint256 amount) external returns (bool); 48 | 49 | /** 50 | * @dev Moves `amount` tokens from `sender` to `recipient` using the 51 | * allowance mechanism. `amount` is then deducted from the caller's 52 | * allowance. 53 | * 54 | * Returns a boolean value indicating whether the operation succeeded. 55 | * 56 | * Emits a {Transfer} event. 57 | */ 58 | function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); 59 | 60 | /** 61 | * @dev Emitted when `value` tokens are moved from one account (`from`) to 62 | * another (`to`). 63 | * 64 | * Note that `value` may be zero. 65 | */ 66 | event Transfer(address indexed from, address indexed to, uint256 value); 67 | 68 | /** 69 | * @dev Emitted when the allowance of a `spender` for an `owner` is set by 70 | * a call to {approve}. `value` is the new allowance. 71 | */ 72 | event Approval(address indexed owner, address indexed spender, uint256 value); 73 | } 74 | 75 | contract VaultTmp { 76 | 77 | address public immutable SYNASSETS; 78 | 79 | constructor ( address _SYNASSETS ) { 80 | require( _SYNASSETS != address(0) ); 81 | SYNASSETS = _SYNASSETS; 82 | } 83 | 84 | function withdraw(address token) external { 85 | require(msg.sender == SYNASSETS); 86 | require( 87 | IERC20(token).transfer(msg.sender, IERC20(token).balanceOf(address(this))) 88 | ); 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /contracts/StakingWarmup.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity 0.7.5; 3 | 4 | 5 | interface IERC20 { 6 | function decimals() external view returns (uint8); 7 | /** 8 | * @dev Returns the amount of tokens in existence. 9 | */ 10 | function totalSupply() external view returns (uint256); 11 | 12 | /** 13 | * @dev Returns the amount of tokens owned by `account`. 14 | */ 15 | function balanceOf(address account) external view returns (uint256); 16 | 17 | /** 18 | * @dev Moves `amount` tokens from the caller's account to `recipient`. 19 | * 20 | * Returns a boolean value indicating whether the operation succeeded. 21 | * 22 | * Emits a {Transfer} event. 23 | */ 24 | function transfer(address recipient, uint256 amount) external returns (bool); 25 | 26 | /** 27 | * @dev Returns the remaining number of tokens that `spender` will be 28 | * allowed to spend on behalf of `owner` through {transferFrom}. This is 29 | * zero by default. 30 | * 31 | * This value changes when {approve} or {transferFrom} are called. 32 | */ 33 | function allowance(address owner, address spender) external view returns (uint256); 34 | 35 | /** 36 | * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. 37 | * 38 | * Returns a boolean value indicating whether the operation succeeded. 39 | * 40 | * IMPORTANT: Beware that changing an allowance with this method brings the risk 41 | * that someone may use both the old and the new allowance by unfortunate 42 | * transaction ordering. One possible solution to mitigate this race 43 | * condition is to first reduce the spender's allowance to 0 and set the 44 | * desired value afterwards: 45 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 46 | * 47 | * Emits an {Approval} event. 48 | */ 49 | function approve(address spender, uint256 amount) external returns (bool); 50 | 51 | /** 52 | * @dev Moves `amount` tokens from `sender` to `recipient` using the 53 | * allowance mechanism. `amount` is then deducted from the caller's 54 | * allowance. 55 | * 56 | * Returns a boolean value indicating whether the operation succeeded. 57 | * 58 | * Emits a {Transfer} event. 59 | */ 60 | function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); 61 | 62 | /** 63 | * @dev Emitted when `value` tokens are moved from one account (`from`) to 64 | * another (`to`). 65 | * 66 | * Note that `value` may be zero. 67 | */ 68 | event Transfer(address indexed from, address indexed to, uint256 value); 69 | 70 | /** 71 | * @dev Emitted when the allowance of a `spender` for an `owner` is set by 72 | * a call to {approve}. `value` is the new allowance. 73 | */ 74 | event Approval(address indexed owner, address indexed spender, uint256 value); 75 | } 76 | 77 | contract StakingWarmup { 78 | 79 | address public immutable staking; 80 | address public immutable sSYNASSETS; 81 | 82 | constructor ( address _staking, address _sSYNASSETS ) { 83 | require( _staking != address(0) ); 84 | staking = _staking; 85 | require( _sSYNASSETS != address(0) ); 86 | sSYNASSETS = _sSYNASSETS; 87 | } 88 | 89 | function retrieve( address _staker, uint _amount ) external { 90 | require( msg.sender == staking ); 91 | IERC20( sSYNASSETS ).transfer( _staker, _amount ); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /test/utils.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require("hardhat"); 2 | 3 | const BN = require("bn.js"); 4 | 5 | async function advanceBlock() { 6 | return ethers.provider.send("evm_mine"); 7 | } 8 | 9 | // Advance the block to the passed height 10 | async function advanceBlockTo(target) { 11 | if (!BN.isBN(target)) { 12 | // eslint-disable-next-line no-param-reassign 13 | target = new BN(target); 14 | } 15 | 16 | const currentBlock = await latestBlock(); 17 | const start = Date.now(); 18 | let notified; 19 | if (target.lt(currentBlock)) 20 | throw Error( 21 | `Target block #(${target}) is lower than current block #(${currentBlock})` 22 | ); 23 | // eslint-disable-next-line no-await-in-loop 24 | while ((await latestBlock()).lt(target)) { 25 | if (!notified && Date.now() - start >= 5000) { 26 | notified = true; 27 | console.log("advancing too far will slow this test down"); 28 | } 29 | // eslint-disable-next-line no-await-in-loop 30 | await advanceBlock(); 31 | } 32 | } 33 | 34 | // Returns the time of the last mined block in seconds 35 | async function latest() { 36 | const block = await ethers.provider.getBlock("latest"); 37 | return new BN(block.timestamp); 38 | } 39 | 40 | async function latestBlock() { 41 | const block = await ethers.provider.getBlock("latest"); 42 | return new BN(block.number); 43 | } 44 | // Increases ganache time by the passed duration in seconds 45 | async function increase(duration) { 46 | if (!BN.isBN(duration)) { 47 | duration = new BN(duration); 48 | } 49 | 50 | if (duration.isNeg()) 51 | throw Error(`Cannot increase time by a negative amount (${duration})`); 52 | 53 | await ethers.provider.send("evm_increaseTime", [duration.toNumber()]); 54 | 55 | await advanceBlock(); 56 | } 57 | 58 | /** 59 | * Beware that due to the need of calling two separate ganache methods and rpc calls overhead 60 | * it's hard to increase time precisely to a target point so design your test to tolerate 61 | * small fluctuations from time to time. 62 | * 63 | * @param target time in seconds 64 | */ 65 | async function increaseTo(target) { 66 | if (!BN.isBN(target)) { 67 | target = new BN(target); 68 | } 69 | 70 | const now = await latest(); 71 | 72 | if (target.lt(now)) 73 | throw Error( 74 | `Cannot increase current time (${now}) to a moment in the past (${target})` 75 | ); 76 | const diff = target.sub(now); 77 | return increase(diff); 78 | } 79 | 80 | const duration = { 81 | seconds(val) { 82 | return new BN(val); 83 | }, 84 | minutes(val) { 85 | return new BN(val).mul(this.seconds("60")); 86 | }, 87 | hours(val) { 88 | return new BN(val).mul(this.minutes("60")); 89 | }, 90 | days(val) { 91 | return new BN(val).mul(this.hours("24")); 92 | }, 93 | weeks(val) { 94 | return new BN(val).mul(this.days("7")); 95 | }, 96 | years(val) { 97 | return new BN(val).mul(this.days("365")); 98 | }, 99 | }; 100 | 101 | function approximately(num1,num2,precision) { 102 | num1 = BigInt(num1); 103 | num2 = BigInt(num2); 104 | 105 | let diff = num1 - num2; 106 | diff = diff > 0n ? diff : -diff; 107 | 108 | return diff === 0n ? true : diff < num1 / precision; 109 | } 110 | 111 | module.exports = { 112 | advanceBlock, 113 | advanceBlockTo, 114 | latest, 115 | latestBlock, 116 | increase, 117 | increaseTo, 118 | duration, 119 | approximately, 120 | }; 121 | -------------------------------------------------------------------------------- /scripts/ExportTokenSaleData.js: -------------------------------------------------------------------------------- 1 | // We require the Hardhat Runtime Environment explicitly here. This is optional 2 | // but useful for running the script in a standalone fashion through `node